From 4ffb4e10b482e6dd1023956f805c009abfe7e23c Mon Sep 17 00:00:00 2001 From: filesite Date: Thu, 26 Sep 2024 09:43:34 +0800 Subject: [PATCH] m3u8 support --- lib/DirScanner.php | 2 +- themes/beauty/controller/ListController.php | 7 ++ themes/beauty/controller/M3u8Controller.php | 116 ++++++++++++++++++++ themes/beauty/controller/SiteController.php | 5 +- themes/beauty/views/site/index.php | 8 +- themes/beauty/views/site/player.php | 5 +- www/js/beauty.js | 18 ++- 7 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 themes/beauty/controller/M3u8Controller.php diff --git a/lib/DirScanner.php b/lib/DirScanner.php index ec197ca..8f19827 100644 --- a/lib/DirScanner.php +++ b/lib/DirScanner.php @@ -355,7 +355,7 @@ Class DirScanner { //根据文件生成防盗链网址 //参考:https://nginx.org/en/docs/http/ngx_http_secure_link_module.html#secure_link //防盗链参数名:md5, expires - protected function getSecureLink($path) { + public function getSecureLink($path) { $expires = time() + $this->nginxSecureTimeout; $originStr = str_replace([ '{secure_link_expires}', diff --git a/themes/beauty/controller/ListController.php b/themes/beauty/controller/ListController.php index 1c7e31b..67944ea 100644 --- a/themes/beauty/controller/ListController.php +++ b/themes/beauty/controller/ListController.php @@ -5,6 +5,7 @@ require_once __DIR__ . '/../../../lib/DirScanner.php'; require_once __DIR__ . '/../../../plugins/Parsedown.php'; require_once __DIR__ . '/../../../plugins/Common.php'; +require_once __DIR__ . '/../../../plugins/Html.php'; Class ListController extends Controller { @@ -215,6 +216,12 @@ Class ListController extends Controller { } if (!empty($item['extension']) && in_array($item['extension'], $videoExts)) { + if ($item['extension'] == 'm3u8') { + $item['path'] .= "&cid={$cacheParentDataId}"; + } + + $item['videoType'] = Html::getMediaSourceType($item['extension']); + array_push($videos, $item); $index ++; } diff --git a/themes/beauty/controller/M3u8Controller.php b/themes/beauty/controller/M3u8Controller.php new file mode 100644 index 0000000..0ba3bd9 --- /dev/null +++ b/themes/beauty/controller/M3u8Controller.php @@ -0,0 +1,116 @@ +get('id', ''); + $cacheParentDataId = $this->get('cid', ''); + if (empty($videoId) || empty($cacheParentDataId)) { + throw new Exception("参数缺失!", 403); + } + + //TODO: 防盗链检查 + + + //渲染m3u8内容 + $cacheSeconds = 86400; + $cachedParentData = Common::getCacheFromFile($cacheParentDataId, $cacheSeconds); + if (empty($cachedParentData)) { + $err = '缓存数据已失效,如果重新点击目录依然打不开,请联系管理员。'; + throw new Exception($err, 404); + } + + if (empty($cachedParentData[$videoId])) { + $erro = "缓存数据中找不到当前视频,请返回上一页重新进入!"; + throw new Exception($err, 404); + }else if (!empty($cachedParentData)) { + $m3u8 = $cachedParentData[$videoId]; + $m3u8Content = $this->getM3u8Content($m3u8['realpath'], $cachedParentData); + if (!empty($m3u8Content)) { + return $this->renderM3u8($m3u8Content); + }else { + $err = 'm3u8内容为空!'; + throw new Exception($err, 500); + } + } + } + + //生成m3u8内容,支持ts防盗链 + /** + * M3u8 content sample: +#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-TARGETDURATION:6 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:5, +0.ts +#EXTINF:5, +1.ts +#EXTINF:5, +2.ts +#EXTINF:5, +3.ts +#EXTINF:5, +4.ts +#EXTINF:0.31697, +5.ts +#EXT-X-ENDLIST + **/ + protected function getM3u8Content($m3u8_realpath, $cachedParentData = array()) { + $m3u8Content = file_get_contents($m3u8_realpath); + if (empty($m3u8Content) || strpos($m3u8Content, 'EXTM3U') === false) { + return false; + } + + $lines = preg_split("/[\r\n]/", $m3u8Content); + + $newContent = ''; + foreach($lines as $index => $line) { + if (strpos($line, '.ts') !== false) { + $newContent .= $this->getRelativePathOfTs($line, $m3u8_realpath, $cachedParentData) . "\n"; + }else if (!empty($line)) { + $newContent .= $line . "\n"; + } + } + + return $newContent; + } + + //返回ts相对当前m3u8文件的相对路径 + //TODO: 支持防盗链 + protected function getRelativePathOfTs($ts_filename, $m3u8_realpath, $cachedParentData = array()) { + if (!empty($cachedParentData)) { + $matchedTs = null; + foreach($cachedParentData as $item) { + if ($item['extension'] == 'ts' && strpos($item['path'], $ts_filename) !== false) { + $matchedTs = $item; + break; + } + } + + if (!empty($matchedTs)) { + return $matchedTs['path']; + }else { + $webroot = FSC::$app['config']['content_directory']; + $rootDir = __DIR__ . '/../../../www/' . $webroot; + $rootDir = realpath($rootDir); + $m3u8Dir = dirname($m3u8_realpath); + $relativeDir = str_replace("{$rootDir}/", '', $m3u8Dir); + return "/{$webroot}{$relativeDir}/{$ts_filename}"; + } + } + + return dirname($m3u8_realpath) . "/{$ts_filename}"; + } + +} \ No newline at end of file diff --git a/themes/beauty/controller/SiteController.php b/themes/beauty/controller/SiteController.php index 22b7df8..7f491ea 100644 --- a/themes/beauty/controller/SiteController.php +++ b/themes/beauty/controller/SiteController.php @@ -581,9 +581,12 @@ Class SiteController extends Controller { //增加文件后缀格式检查,区分:mp4, mov, m3u8 $videoExtension = pathinfo($arr['path'], PATHINFO_EXTENSION); + //支持m3u8地址:/m3u8/?id=xxx + if ($videoFilename == 'm3u8') { + $videoExtension = 'm3u8'; + } $videoSourceType = Html::getMediaSourceType($videoExtension); - //获取联系方式 $maxScanDeep = 0; //最大扫描目录级数 $cacheKey = $this->getCacheKey('root', 'readme', $maxScanDeep); diff --git a/themes/beauty/views/site/index.php b/themes/beauty/views/site/index.php index 0fed848..30eb544 100644 --- a/themes/beauty/views/site/index.php +++ b/themes/beauty/views/site/index.php @@ -370,7 +370,13 @@ eof; eof; }else if (in_array($file['extension'], $videoExts)) { //输出视频 - $videoUrl = urlencode($file['path']); + //m3u8支持 + if ($file['extension'] == 'm3u8') { + $videoUrl = urlencode("{$file['path']}&cid={$viewData['cacheDataId']}"); + }else { + $videoUrl = urlencode($file['path']); + } + $linkUrl = "/site/player?id={$file['id']}&pid={$file['pid']}&cid={$viewData['cacheDataId']}&url={$videoUrl}"; if ($viewData['showType'] == 'video') { $linkUrl .= "&page={$viewData['page']}&limit={$viewData['pageSize']}"; diff --git a/themes/beauty/views/site/player.php b/themes/beauty/views/site/player.php index 6024cba..c5ef307 100644 --- a/themes/beauty/views/site/player.php +++ b/themes/beauty/views/site/player.php @@ -36,9 +36,10 @@ class="video-js vjs-big-play-centered vjs-fluid vjs-16-9" controls playsinline - poster="" + poster="" + data-src="" + data-type="" id="my-player"> -
diff --git a/www/js/beauty.js b/www/js/beauty.js index 89a3c70..4b88056 100644 --- a/www/js/beauty.js +++ b/www/js/beauty.js @@ -610,6 +610,18 @@ if ($('#my-player').length > 0 && typeof(videojs) != 'undefined') { setTimeout(takeScreenshot, screenshot_start); }); + //自动播放 + try{ + var videoSrc = $('#my-player').attr('data-src'), + videoType = $('#my-player').attr('data-type'); + myPlayer.src({ + src: videoSrc, + type: videoType + }); + }catch(err) { + console.error('自动播放视频失败!', err); + } + //生成封面图 $('.btn-snapshot').click(function(e) { var clickedBtn = $(e.target); @@ -719,7 +731,11 @@ if ($('#my-player').length > 0 && typeof(videojs) != 'undefined') { } if (nextVideo) { - myPlayer.src(nextVideo.path); + //TODO: 支持其它格式,fix /m3u8/这种路径 + myPlayer.src({ + src: nextVideo.path, + type: nextVideo.videoType + }); $('.navbar-header .videotitle').text(nextVideo.filename); } };