首页 > 其他分享 >如何开发视频上传和播放功能时,既省钱又体验好?

如何开发视频上传和播放功能时,既省钱又体验好?

时间:2023-06-04 17:45:02浏览次数:51  
标签:function 上传 oss data 省钱 result 播放 config

前言:

  现如今,大部分带内容的网站或应用都有视频区了,不说是大厂平台,就连个人开发者也相继在自己网站或小程序上迭代出视频板块。那既然有了视频模块,除个性化推荐,智能审核等这种费钱又耗时的功能外(个人开发者暂缓)。最基本的视频上传,视频播放自然必不可少吧。

  既然要强调省钱,我当前不会对接点播服务了。毕竟为了有一定的审核和推荐功能,我打算做人工审核。那剩下的关于播放有一定的体验度,还得要用一下OSS了(还是要花一点嘛)。因为上传有现成的分片上传,播放有HLS流,以下着重讲关于视频播放的优化,上传部分就说一下思路哦。

 

视频上传:

  因为网站关于上传只有一些图片,所以只用了OSS的常规文件上传。但是视频,不说长视频,现在一些稍微几十秒的短视频动则几十兆上百兆,更别说高清的。而且早期的上传是放在服务端完成,所以当客户端提交大型文件时,不光nginx(413 Request Entity Too Large)和fpm会报错,到了OSS客户端调用也会报错(Allowed memory size of 268435456 bytes exhausted (tried to allocate 67108896 bytes))。

  开始是尝试在服务端改成分片上传,但是测试时发现,每次提交文件过来时都要先将视频本地化,存在服务器上后再分片提交到OSS,回调成功删除服务器文件,并且要调整nginx等的提交的最大值限制。最后就决定把上传部分放到了前端,服务器只提供OSS的临时访问凭证和接收上传成功回调。

 

视频播放:

  关于视频播放,网站早期的做法是将OSS的视频地址直接丢到前端的video标签中,当在手机播放时会出现卡顿或播放缓慢等问题。最后决定使用OSS的HLS的构建,就是在后台将视频推流一份在OSS的LiveChannel中,前端通过读取m3u8播放视频。

思路:

1. 前端上传视频,后台审核成功调用OSS的LiveChannel创建。

2. 根据通道创建返回的推流地址和播放地址,存入数据库。

3. 在服务端起一个进程用FFMPEG进行视频推流。

4. 前端播放视频时选择m3u8的方式。

 

示例代码:

1. 前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>普通上传模板,需要修改成分片上传</title>
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <style>
        #content {
            border: 1px solid saddlebrown;
            padding: 16px;
            border-radius: 2px
        }

        .list {
            top: 15px;
            width: 140px;
            height: 40px;
            border: 1px solid #0082E6;
            display: inline-block;
            border-radius: 2px;
            position: relative;
            line-height: 40px;
        }

        #file {
            position: absolute;
            opacity: 0;
            color: white;
            width: 100%;
            height: 100%;
            z-index: 100;
        }

        .list span {
            display: inline-block;
            text-align: center;
            width: 100%;
            line-height: 40px;
            position: absolute;
            color: #0082E6;
        }

        video {
            margin-top: 8px;
            border-radius: 4px;
        }

        ._p {
            margin: 14px;
        }

        ._p input {
            display: inline-block;
            width: 70%;
            margin-left: 6px;
        }

        ._p span {
            font-size: 15px;
        }
    </style>

</head>
<body>

<div id="content">
    <p class="_p"><span>视频标题</span>:
        <input id="title" type="text" class="form-control" placeholder="请输入视频名称">
    </p>

    <p class="_p">
        <span>选择视频: </span>

        <!--文件选择按钮-->
        <a class="list" href="javascript:;">
            <input id="file" type="file" name="myfile" onchange="UpladFile();"/>
            <span>选择视频</span>
        </a>
        <!--上传速度显示-->

        <span id="time"></span>
    </p>

    <!--显示消失-->
    <ul class="el-upload-list el-upload-list--text" style="display:  none;">
        <li tabindex="0" class="el-upload-list__item is-success">
            <a class="el-upload-list__item-name">
                <i class="el-icon-document"></i>
                <span id="videoName">food.jpeg</span>
            </a>
            <label class="el-upload-list__item-status-label">
                <i class="el-icon-upload-success el-icon-circle-check"></i>
            </label>
            <i class="el-icon-close" onclick="del();"></i>
            <i class="el-icon-close-tip"></i>
        </li>
    </ul>

    <!--进度条-->
    <div class="el-progress el-progress--line" style="display: none;">
        <div class="el-progress-bar">
            <div class="el-progress-bar__outer" style="height: 6px;">
                <div class="el-progress-bar__inner" style="width: 0%;">
                </div>
            </div>
        </div>
        <div class="el-progress__text" style="font-size: 14.4px;">0%</div>
    </div>

    <p class="_p"><span>上传视频</span>:
        <button class="btn btn-primary" type="button" onclick="sub();">上传</button>
    </p>

    <!--预览框-->
    <div class="preview">

    </div>
</div>

<script>
    var xhr;//异步请求对象
    var ot; //时间
    var oloaded;//大小

    //上传文件方法
    function UpladFile() {
        var fileObj = document.getElementById("file").files[0]; // js 获取文件对象

        if (fileObj.name) {
            $(".el-upload-list").css("display", "block");
            $(".el-upload-list li").css("border", "1px solid #20a0ff");
            $("#videoName").text(fileObj.name);
        } else {
            alert("请选择文件");
        }
    }

    /*点击取消*/
    function del() {
        $("#file").val('');
        $(".el-upload-list").css("display", "none");
    }

    /*点击提交*/
    function sub() {
        var fileObj = document.getElementById("file").files[0]; // js 获取文件对象
        if (fileObj == undefined || fileObj == "") {
            alert("请选择文件");
            return false;
        }
        var title = $.trim($("#title").val());
        if (title == '') {
            alert("请填写视频标题");
            return false;
        }

        var url = "/uploadFile"; // 接收上传文件的后台地址
        var form = new FormData();  // FormData 对象
        form.append("mf", fileObj); // 文件对象
        form.append("title", title); // 标题
        xhr = new XMLHttpRequest(); // XMLHttpRequest 对象
        xhr.open("post", url, true); // post方式,url为服务器请求地址,true 该参数规定请求是否异步处理。
        xhr.onload = uploadComplete; // 请求完成
        xhr.onerror = uploadFailed;  // 请求失败
        xhr.upload.onprogress = progressFunction; //【上传进度调用方法实现】
        xhr.upload.onloadstart = function () {    //上传开始执行方法
            ot = new Date().getTime(); //设置上传开始时间
            oloaded = 0; //设置上传开始时,以上传的文件大小为0
        };
        xhr.send(form); //开始上传,发送form数据
    }

    //上传进度实现方法,上传过程中会频繁调用该方法
    function progressFunction(evt) {
        // event.total是需要传输的总字节,event.loaded是已经传输的字节。如果event.lengthComputable不为真,则event.total等于0
        if (evt.lengthComputable) {
            $(".el-progress--line").css("display", "block");
            /*进度条显示进度*/
            $(".el-progress-bar__inner").css("width", Math.round(evt.loaded / evt.total * 100) + "%");
            $(".el-progress__text").html(Math.round(evt.loaded / evt.total * 100) + "%");
        }

        var time = document.getElementById("time");
        var nt = new Date().getTime(); //获取当前时间
        var pertime = (nt - ot) / 1000; //计算出上次调用该方法时到现在的时间差,单位为s
        ot = new Date().getTime(); //重新赋值时间,用于下次计算

        var perload = evt.loaded - oloaded; //计算该分段上传的文件大小,单位b
        oloaded = evt.loaded; //重新赋值已上传文件大小,用以下次计算

        //上传速度计算
        var speed = perload / pertime; //单位b/s
        var bspeed = speed;
        var units = 'b/s'; //单位名称
        if (speed / 1024 > 1) {
            speed = speed / 1024;
            units = 'k/s';
        }
        if (speed / 1024 > 1) {
            speed = speed / 1024;
            units = 'M/s';
        }
        speed = speed.toFixed(1);
        //剩余时间
        var resttime = ((evt.total - evt.loaded) / bspeed).toFixed(1);
        time.innerHTML = '上传速度:' + speed + units + ',剩余时间:' + resttime + 's';
        if (bspeed == 0)
            time.innerHTML = '上传已取消';
    }

    //上传成功响应
    function uploadComplete(evt) {
        //服务断接收完文件返回的结果  注意返回的字符串要去掉双引号
        if (evt.target.responseText) {
            var responseData = JSON.parse(evt.target.responseText);
            var mediaUrl = responseData.data.url;
            alert("上传成功!");
            $(".preview").append("<video  controls='' autoplay='' name='media'><source src=" + mediaUrl + " type='video/mp4'></video>");
        } else {
            alert("上传失败");
        }
    }

    //上传失败
    function uploadFailed(evt) {
        alert("上传失败!");
    }
</script>

</body>
</html>

2. 后端API层

<?php

namespace app\index\controller;

use think\Db;
use think\Console;
use think\Controller;


class OssTest extends Controller
{
    // 获取临时访问凭证
    public function getTempPlicy()
    {
        $oss = \oss\OssFactory::factory("AliOss");

        $result = $oss->getRamPolicy();

        return $result;
    }

    // 简单上传接口
    public function uploadFile()
    {
        $files = request()->file();
        $input['title'] = input('post.title');
        $uploadFiled = $files['mf'];

        // 获取文件信息
        $fileInfo = $uploadFiled->getInfo();
        $oss = \oss\OssFactory::factory("AliOss");

        $result = $oss->upload($uploadFiled, "");

        //$width = getimagesize($imgUrl)[0];
        //$height = getimagesize($imgUrl)[1];

        return $result;
    }

    // 分片上传接口
    public function multipartUpload()
    {
        $files = request()->file();
        $input['title'] = input('post.title');
        $uploadFiled = $files['mf'];

        $oss = \oss\OssFactory::factory("AliOss");

        $result = $oss->multipartUpload();

        return $result;
    }

    // 分段Push
    public function multionPush()
    {
        global $_W;
        global $_GPC;
        $fileArr = $_FILES['mf'];
        $title = $_GPC['title'];

        // 设置预览目录,上传成功的路径
        $previewPath = "../shiping/";
        $ext = pathinfo($fileArr['name'], PATHINFO_EXTENSION);//获取当前上传文件扩展名
        $arrExt = array('3gp','rmvb','flv','wmv','avi','mkv','mp4','mp3','wav',);

        if(!in_array($ext,$arrExt)) {
            exit(json_encode(-1,JSON_UNESCAPED_UNICODE)); //视/音频或采用了不合适的扩展名!
        } else {
            //文件上传到预览目录
            $previewName = 'pre_'.md5(mt_rand(1000,9999)).time().'.'.$ext; //文件重命名
            $previewSrc = $previewPath.$previewName;

            if(move_uploaded_file($fileArr['tmp_name'],$previewSrc)){ // 上传文件操作,上传失败的操作
                exit($previewName);
            } else {
                //上传成功的失败的操作
                exit(json_encode(0,JSON_UNESCAPED_UNICODE));
            }
        }
    }

    // 分页push页面
    public function multionPushView()
    {

    }

    // 使用ffmpeg开始推流
    public function pushLive()
    {
        //$command = "ffmpeg -re -i https://resources.huanhuanhuishou.com/global_source/B7689D4E-ED00-B402-EBD9-4F52C3B97247.mp4 -c copy -f flv \"rtmp://hhbusiness.oss-cn-shenzhen.aliyuncs.com/live/test_rtmp_live?playlistName=iphone8.m3u8&OSSAccessKeyId=LTAIp7VhX2YoazcF&Expires=1685586947&Signature=ovAOELN2anMs1Y%2Fzam4BhhKzZYE%3D\"";
        //exec($command, $output, $return_var);

        //$result = exec('ipconfig', $output);
        //var_dump($output[4]);die;
    }

    // 创建推流通道
    public function putLiveChannel()
    {
        $oss = \oss\OssFactory::factory("AliOss");

        $result = $oss->putLiveChannel('iphone8');

        return $this->jsonData(200, 'ok', $result);
    }

    // 获取推流加签地址
    public function getSignRtmpUrl()
    {
        $oss = \oss\OssFactory::factory("AliOss");

        $params = array('params' => array('playlistName' => 'newest.m3u8'));

        $result = $oss->getSignRtmpUrl($params);

        return $this->jsonData(200, 'ok', $result);
    }

    // 获取推流上传通道历史记录
    public function getLiveChannelHistory()
    {
        $oss = \oss\OssFactory::factory("AliOss");

        //$params = array('params' => array('playlistName' => 'newest.m3u8'));

        $result = $oss->getLiveChannelHistory();

        return $this->jsonData(200, 'ok', $result);
    }

    // 查询推流上传信息
    public function getLiveChannelInfo()
    {
        $oss = \oss\OssFactory::factory("AliOss");

        $result = $oss->getLiveChannelInfo();

        return $this->jsonData(200, 'ok', $result);
    }

    // 查询m3u8的推流上传状态
    public function getLiveChannelStatus()
    {
        $oss = \oss\OssFactory::factory("AliOss");

        $result = $oss->getLiveChannelStatus();

        return $this->jsonData(200, 'ok', $result);
    }

}

3. OSS操作层

<?php

namespace oss;

require_once 'lib/aliyun-oss/autoload.php';

use OSS\OssClient;
use OSS\Model\LiveChannelConfig;
use OSS\Core\OssException;
use OSS\Core\OssUtil;
use Mimey\MimeTypes;
use AlibabaCloud\SDK\Sts\V20150401\Sts;
use Darabonba\OpenApi\Models\Config;
use AlibabaCloud\SDK\Sts\V20150401\Models\AssumeRoleRequest;
use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions;

/**
 * 阿里云OSS文件传输
 */
class AliOss extends OssBase
{

    public $config = [];

    public function __construct()
    {
        $this->config = include 'config/ali_oss_config.php';
    }

    // 获取临时访问凭证
    // composer require alibabacloud/sts-20150401
    public function getRamPolicy()
    {
        $config = $this->config;

        try {
            // 填写步骤1创建的RAM用户AccessKey。
            $config = new Config([
                "accessKeyId" => $config['AccessKeyID'],
                "accessKeySecret" => $config['AccessKeySecret']
            ]);
            //
            $config->endpoint = "sts.cn-shenzhen.aliyuncs.com";
            $client = new Sts($config);

            $assumeRoleRequest = new AssumeRoleRequest([
                // roleArn填写步骤2获取的角色ARN,例如acs:ram::175708322470****:role/ramtest。
                "roleArn" => "acs:ram::175708322470****:role/ramtest",
                // roleSessionName用于自定义角色会话名称,用来区分不同的令牌,例如填写为sessiontest。
                "roleSessionName" => "sessiontest",
                // durationSeconds用于设置临时访问凭证有效时间单位为秒,最小值为900,最大值以当前角色设定的最大会话时间为准。本示例指定有效时间为3000秒。
                "durationSeconds" => 3000,
                // policy填写自定义权限策略,用于进一步限制STS临时访问凭证的权限。如果不指定Policy,则返回的STS临时访问凭证默认拥有指定角色的所有权限。
                // 临时访问凭证最后获得的权限是步骤4设置的角色权限和该Policy设置权限的交集。
                //"policy" => ""
            ]);
            $runtime = new RuntimeOptions([]);
            $result = $client->assumeRoleWithOptions($assumeRoleRequest, $runtime);
            printf("AccessKeyId:" . $result->body->credentials->accessKeyId . PHP_EOL);
            printf("AccessKeySecret:" . $result->body->credentials->accessKeySecret . PHP_EOL);
            printf("Expiration:" . $result->body->credentials->expiration . PHP_EOL);
            printf("SecurityToken:" . $result->body->credentials->securityToken . PHP_EOL);
        } catch (Exception $e) {
            printf($e->getMessage() . PHP_EOL);
        }

        return $result;
    }

    public function upload($file, $dir = "")
    {
        $config = $this->config;

        $fileInfo = $file->getInfo();

        // 获取文件扩展名
        //$extName = pathinfo($fileInfo['name'], PATHINFO_EXTENSION);

        if ($dir === "") {
            $dir = "tmp/";
        }

        $fileName = $dir . $this->getFileName($fileInfo);

        $res['code'] = 1;

        try {
            $ossClient = new OssClient($config['AccessKeyID'], $config['AccessKeySecret'], $config['EndPoint']);

            $result = $ossClient->uploadFile($config['Bucket'], $fileName, $fileInfo['tmp_name']);

            $res['data']['url'] = $result['info']['url'];
            $res['data']['path'] = $fileName;

        } catch (OssException $e) {

            $res['code'] = 0;
            $res['message'] = $e->getMessage();
        }

        return $res;
    }

    // 单文件流上传
    public function objectUpload($content, $file_name = '')
    {
        $config = $this->config;
        $res['code'] = 1;
        $res['message'] = '';
        try {
            $ossClient = new OssClient($config['AccessKeyID'], $config['AccessKeySecret'], $config['EndPoint']);

            $result = $ossClient->putObject($config['Bucket'], $file_name, $content);

            $res['data']['url'] = $result['info']['url'];
            $res['data']['path'] = explode("/", $file_name)[1];

        } catch (OssException $e) {
            $res['code'] = 0;
            $res['message'] = $e->getMessage();
        }
        return $res;
    }

    // 分段上传
    public function multipartUpload()
    {
        $config = $this->config;
        $ossClient = new OssClient($config['AccessKeyID'], $config['AccessKeySecret'], $config['EndPoint']);

        // 填写Bucket名称,例如examplebucket。
        $bucket = $config['Bucket'];
        //填写不包含Bucket名称在内的Object完整路径,例如exampledir/exampleobject.txt。
        $object = $config['Bucket'] . 'lv_0_20230528160220.mp4';
        // 填写本地文件的完整路径。
        $uploadFile = 'D:\\lv_0_20230528160220.mp4';
        $initOptions = array(
            OssClient::OSS_HEADERS => array(
                // 指定该Object被下载时的网页缓存行为。
                // 'Cache-Control' => 'no-cache',
                // 指定该Object被下载时的名称。
                // 'Content-Disposition' => 'attachment;filename=oss_download.jpg',
                // 指定该Object被下载时的内容编码格式。
                // 'Content-Encoding' => 'utf-8',
                // 指定过期时间,单位为毫秒。
                // 'Expires' => 150,
                // 指定初始化分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。
                //'x-oss-forbid-overwrite' => 'true',
                // 指定上传该Object的每个part时使用的服务器端加密方式。
                // 'x-oss-server-side-encryption'=> 'KMS',
                // 指定Object的加密算法。
                // 'x-oss-server-side-data-encryption'=>'SM4',
                // 指定KMS托管的用户主密钥。
                //'x-oss-server-side-encryption-key-id' => '9468da86-3509-4f8d-a61e-6eab1eac****',
                // 指定Object的存储类型。
                // 'x-oss-storage-class' => 'Standard',
                // 指定Object的对象标签,可同时设置多个标签。
                // 'x-oss-tagging' => 'TagA=A&TagB=B',
            ),
        );

        try {
            //返回uploadId。uploadId是分片上传事件的唯一标识,您可以根据uploadId发起相关的操作,如取消分片上传、查询分片上传等。
            $uploadId = $ossClient->initiateMultipartUpload($bucket, $object, $initOptions);
            print("initiateMultipartUpload OK" . "\n");
        } catch (OssException $e) {
            printf($e->getMessage() . "\n");
            return;
        }

        $partSize = 10 * 1024 * 1024;
        $uploadFileSize = sprintf('%u', filesize($uploadFile));
        $pieces = $ossClient->generateMultiuploadParts($uploadFileSize, $partSize);
        $responseUploadPart = array();
        $uploadPosition = 0;
        $isCheckMd5 = true;
        foreach ($pieces as $i => $piece) {
            $fromPos = $uploadPosition + (integer)$piece[$ossClient::OSS_SEEK_TO];
            $toPos = (integer)$piece[$ossClient::OSS_LENGTH] + $fromPos - 1;
            $upOptions = array(
                // 上传文件。
                $ossClient::OSS_FILE_UPLOAD => $uploadFile,
                // 设置分片号。
                $ossClient::OSS_PART_NUM => ($i + 1),
                // 指定分片上传起始位置。
                $ossClient::OSS_SEEK_TO => $fromPos,
                // 指定文件长度。
                $ossClient::OSS_LENGTH => $toPos - $fromPos + 1,
                // 是否开启MD5校验,true为开启。
                $ossClient::OSS_CHECK_MD5 => $isCheckMd5,
            );
            // 开启MD5校验。
            if ($isCheckMd5) {
                $contentMd5 = OssUtil::getMd5SumForFile($uploadFile, $fromPos, $toPos);
                $upOptions[$ossClient::OSS_CONTENT_MD5] = $contentMd5;
            }
            try {
                // 上传分片。
                $responseUploadPart[] = $ossClient->uploadPart($bucket, $object, $uploadId, $upOptions);
                printf("initiateMultipartUpload, uploadPart - part#{$i} OK\n");
            } catch (OssException $e) {
                printf("initiateMultipartUpload, uploadPart - part#{$i} FAILED\n");
                printf($e->getMessage() . "\n");
                return;
            }

        }

        // $uploadParts是由每个分片的ETag和分片号(PartNumber)组成的数组。
        $uploadParts = array();
        foreach ($responseUploadPart as $i => $eTag) {
            $uploadParts[] = array(
                'PartNumber' => ($i + 1),
                'ETag' => $eTag,
            );
        }

        $comOptions['headers'] = array(
            // 指定完成分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。
            // 'x-oss-forbid-overwrite' => 'true',
            // 如果指定了x-oss-complete-all:yes,则OSS会列举当前uploadId已上传的所有Part,然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。
            // 'x-oss-complete-all'=> 'yes'
        );

        $res = [];
        try {
            // 执行completeMultipartUpload操作时,需要提供所有有效的$uploadParts。OSS收到提交的$uploadParts后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。
            $res = $ossClient->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts, $comOptions);
            printf("Complete Multipart Upload OK\n");
        } catch (OssException $e) {
            printf("Complete Multipart Upload FAILED\n");
            printf($e->getMessage() . "\n");
            return;
        }

        return $res;
    }

    // 列举文件
    public function listBuckets($bucket = "hhbusiness", $prefix = "", $pagesize = 20, $maker = "")
    {
        $config = $this->config;
        $res['code'] = 1;
        $res['message'] = '';
        $res['data'] = "";
        try {
            $ossClient = new OssClient($config['AccessKeyID'], $config['AccessKeySecret'], $config['EndPoint']);
            $delimiter = '/';
            $nextMarker = "";
            //$maxkeys = 1;

            $options = array(
                'delimiter' => $delimiter,
                'prefix' => $prefix,
                'max-keys' => $pagesize,
                'marker' => $maker,
            );
            $result['obj'] = $ossClient->listObjects($bucket, $options);

            $result['next_marker'] = $result['obj']->getNextMarker();

            // 没有值
            if ($result['obj']->getIsTruncated() !== "true") {
                //break;
            }

            $res['code'] = 1;
            $res['data'] = $result;

        } catch (OssException $e) {
            $res['code'] = 0;
            $res['message'] = $e->getMessage();
        }
        return $res;
    }

    public function putLiveChannel($liveChannelName)
    {
        $ossClientConfig = $this->config;

        $config = new LiveChannelConfig(array(
            'description' => 'live channel test',
            'type' => 'HLS',
            'fragDuration' => 2,
            'fragCount' => 5,
            'playListName' => $liveChannelName . '.m3u8'
        ));

        $ossClient = new OssClient($ossClientConfig['AccessKeyID'], $ossClientConfig['AccessKeySecret'], $ossClientConfig['EndPoint']);

        $info = $ossClient->putBucketLiveChannel($ossClientConfig['Bucket'], 'test_rtmp_live', $config);

        $data['channel_name'] = $info->getName();
        $data['channel_description'] = $info->getDescription();
        $data['publishurls'] = $info->getPublishUrls();
        $data['playurls'] = $info->getPlayUrls();

        $data['publishurls_sig'] = $ossClient->signRtmpUrl($ossClientConfig['Bucket'], "test_rtmp_live", 3600, array('params' => array('playlistName' => $liveChannelName . '.m3u8')));

        return $data;
    }

    public function getSignRtmpUrl($param = [])
    {
        $ossClientConfig = $this->config;
        $ossClient = new OssClient($ossClientConfig['AccessKeyID'], $ossClientConfig['AccessKeySecret'], $ossClientConfig['EndPoint']);

        $url = $ossClient->signRtmpUrl($ossClientConfig['Bucket'], "test_rtmp_live", 3600, $param);

        return $url;
    }

    public function getLiveChannelHistory()
    {
        $ossClientConfig = $this->config;

        $ossClient = new OssClient($ossClientConfig['AccessKeyID'], $ossClientConfig['AccessKeySecret'], $ossClientConfig['EndPoint']);

        $history = $ossClient->getLiveChannelHistory($ossClientConfig['Bucket'], "test_rtmp_live");

        $list = [];
        foreach ($history->getLiveRecordList() as $recordList) {
            $data['startTime'] = $recordList->getStartTime();
            $data['endTime'] = $recordList->getEndTime();
            $data['remoteAddr'] = $recordList->getRemoteAddr();

            $list[] = $data;
        }

        return $list;
    }

    public function getLiveChannelInfo()
    {
        $ossClientConfig = $this->config;

        $ossClient = new OssClient($ossClientConfig['AccessKeyID'], $ossClientConfig['AccessKeySecret'], $ossClientConfig['EndPoint']);
        $info = $ossClient->getLiveChannelInfo($ossClientConfig['Bucket'], 'test_rtmp_live');

        $data['description'] = $info->getDescription();
        $data['status'] = $info->getStatus();
        $data['type'] = $info->getType();
        $data['fragDuration'] = $info->getFragDuration();
        $data['fragCount'] = $info->getFragCount();
        $data['playListName'] = $info->getPlayListName();

        return $data;
    }

    public function getLiveChannelStatus()
    {
        $ossClientConfig = $this->config;

        $ossClient = new OssClient($ossClientConfig['AccessKeyID'], $ossClientConfig['AccessKeySecret'], $ossClientConfig['EndPoint']);

        $status = $ossClient->getLiveChannelStatus($ossClientConfig['Bucket'], "test_rtmp_live");

        $data['status'] = $status->getStatus();
        $data['ConnectedTime'] = $status->getConnectedTime();
        $data['VideoWidth'] = $status->getVideoWidth();
        $data['VideoHeight'] = $status->getVideoHeight();
        $data['VideoFrameRate'] = $status->getVideoFrameRate();
        $data['VideoBandwidth'] = $status->getVideoBandwidth();
        $data['VideoCodec'] = $status->getVideoCodec();
        $data['AudioBandwidth'] = $status->getAudioBandwidth();
        $data['AudioSampleRate'] = $status->getAudioSampleRate();
        $data['AdioCodec'] = $status->getAudioCodec();

        return $data;
    }


    private function guid($rand = "")
    {
        $charid = strtoupper(md5(uniqid(mt_rand(), true) . $rand));
        $hyphen = chr(45);// "-"
        $uuid = substr($charid, 0, 8) . $hyphen . substr($charid, 8, 4) . $hyphen . substr($charid, 12, 4) . $hyphen . substr($charid, 16, 4) . $hyphen . substr($charid, 20, 12);

        return $uuid;
    }

    /**
     * 获取指定长度的随机字母数字组合的字符串
     * @param int $length
     * @param bool $length
     * @return string
     */
    private function random($length = 32, $repeat = True)
    {
        $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

        if ($repeat === True) {
            $pool = str_repeat($pool, $length);
        }

        return substr(str_shuffle($pool), 0, $length);
    }


    private function getFileName($file)
    {
        $rand = $this->random();

        $guid = $this->guid($rand);

        //$mime_content_type = mime_content_type($file['tmp_name']);
        $mime_content_type = $extName = pathinfo($file['name'], PATHINFO_EXTENSION);;
        //$mimes = new MimeTypes();

        return $guid . "." . $mime_content_type;
    }

}

4. 推流指令

<?php

namespace app\common\command;

use think\console\Command;
use think\console\Input;
use think\console\Output;

use think\Db;

class PushRtmp extends Command
{
    protected function configure()
    {
        $this->setName('push:rtmp')->setDescription('rtmp推流');
    }

    protected function execute(Input $input, Output $output)
    {
        $command = "ffmpeg -re -i https://www.neiqiaosu.com/global_source/B7689D4E-ED00-B402-EBD9-4F52C3B97247.mp4 -c copy -f flv \"rtmp://def.oss-cn-shenzhen.aliyuncs.com/live/test_rtmp_live?playlistName=newest.m3u8&OSSAccessKeyId=LTAIp7VhX2YoazcF&Expires=1685606516&Signature=ucEuVujPN2AeHFGbpwF%2BgHqfNPk%3D\"";
        $result = exec($command, $output, $resultVar);
        
        var_dump($output);

    }
}

FFmpeg参数说明

frame= 9753 fps= 30 q=-1.0 Lsize=  129340kB time=00:05:25.09 bitrate=3259.2kbits/s speed=   1x

frame= 9753:表示拍摄的帧数为9753

fps=246:表示每秒拍摄246张照片

q=-1.0:表示视频的质量为-1.0,即质量较差

Lsize= 129340kB:表示视频的大小为129340KB

time=00:05:25.09:表示视频的时长为5分25秒9毫秒

bitrate=3259.2kbits/s:表示视频的码率为3259.2KB/s

speed=8.21x:表示视频的播放速度为8.21倍

video:116301kB audio:12596kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.344442%

video:116301kB  			视频数据量:116301kB;
audio:12596kB 音频数据量:12596kB;
subtitle:0kB 字幕数据量:0kB;
other streams:0kB 其他数据量:0kB;
global headers:0kB 全局数据头:0kB;
muxing overhead: 0.344442% 多路复用开销:0.344442%

  

标签:function,上传,oss,data,省钱,result,播放,config
From: https://www.cnblogs.com/zerofc/p/17455888.html

相关文章

  • Dropzone JS 使用指南(文件拖拽上传)
    JavaScript文件拖拽上传插件dropzone.js介绍[url]https://www.renfei.org/blog/dropzone-js-introduction.html[/url]DropzoneJS使用指南(文件拖拽上传)[url]http://www.open-open.com/lib/view/open1448610841329.html[/url]后台资料:基于SpringMVC的......
  • Spring MVC文件上传 文件上传解析 Spring MVC文件上传详解
    首先我要说的是springmvc的核心控制器DispachServlet,这个控制器主要是用来起调度作用,他里面默认就带了一个文件上传的视图解析器,叫multipartResolver,而这个视图解析器SpringMVC又提供了一个默认的实现,叫CommonMultipartResolver,说白了这个实现底层用的就是common-fileupload,......
  • 连珠(有禁五子棋)课程播放平台课件的录制方法
    RenjuClass、浙江教室及其他连珠(有禁五子棋)课程在线学习这是个播放专业连珠课程的学习网站当前上面有近800节以前老牌资深棋手讲的课。有世界冠军爱沙尼亚的Ando九段Tunnet八段、Ants七段,日本的河村九段山口九段冈部宽九段,通讯赛世界冠军张进宇四段、全国冠军戴晓涵六段、吴昊......
  • django 自定义FileField upload_to上传路径
    defuser_directory_path(instance,name):"""clean_data内容:fork,vinclean_data:K:fileV:record1301DL00220230602全部.txtK:nameV:record1301DL00220230602全部.txt"""filename=name[15:2......
  • 使用vscode sftp插件快速上传源码文件
    1.首先安装vscode插件2.使用ctrl+shift+p或者view-commandpalette打开命令面板,输入sftp并按enter键,出现编辑配置文件界面3.输入对应的主机名,密码,或者密钥文件即可{"name":"47.100.101.152","host":"47.100.101.152","protocol":"sftp",......
  • 关于本地代码上传gitee
    一、首先准备两个工具一个是git,一个是tortoisegittortoisegit  链接:https://pan.baidu.com/s/1IubajDKee2TSWVafCiRg_A提取码:xcjqgit链接:https://pan.baidu.com/s/1bcy-JeMfRQs-lbhpnA5bog提取码:xfui(安装步骤省略)二、安装完成右击会出现以下两个东西三、去注......
  • WPF中的Image控件上传,保存,显示头像
    WPF中的Image控件上传,保存,显示头像//选择电脑上的图片显示到Image控件中privatevoidbtnUpload_Click(objectsender,RoutedEventArgse){//打开文件对话框以选择要上传的图片OpenFileDialogopenFileDialog=newOpenFi......
  • WPF播放声音
    WPF播放声音用MediaElement控件,一个放完到下一个  <Windowx:Class="WpfApp1_candel.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"......
  • 微信小程序发布上线全流程(注册/开发/上传审核)
    以下是微信小程序发布上线的详细流程:确认小程序信息:在微信公众平台注册并登录后,进入小程序管理后台,在“开发”->“开发设置”中填写小程序基本信息和配置,包括小程序名称、图标设计、类目选择等。此外,需要在小程序管理后台中配置小程序服务类目和资质认证。开发小程序代码和测试:使用......
  • h5 audio播放声音
    h5audio播放声音http://www.niunan.net/test_audio.html <!DOCTYPEhtml><html><head><metaname="viewport"content="width=device-width,initial-scale=1.0"><metacharset="utf-8"/><......