Browse Source

m3u8 support

master
filesite 2 months ago
parent
commit
4ffb4e10b4
  1. 2
      lib/DirScanner.php
  2. 7
      themes/beauty/controller/ListController.php
  3. 116
      themes/beauty/controller/M3u8Controller.php
  4. 5
      themes/beauty/controller/SiteController.php
  5. 8
      themes/beauty/views/site/index.php
  6. 5
      themes/beauty/views/site/player.php
  7. 18
      www/js/beauty.js

2
lib/DirScanner.php

@ -355,7 +355,7 @@ Class DirScanner {
//根据文件生成防盗链网址 //根据文件生成防盗链网址
//参考:https://nginx.org/en/docs/http/ngx_http_secure_link_module.html#secure_link //参考:https://nginx.org/en/docs/http/ngx_http_secure_link_module.html#secure_link
//防盗链参数名:md5, expires //防盗链参数名:md5, expires
protected function getSecureLink($path) { public function getSecureLink($path) {
$expires = time() + $this->nginxSecureTimeout; $expires = time() + $this->nginxSecureTimeout;
$originStr = str_replace([ $originStr = str_replace([
'{secure_link_expires}', '{secure_link_expires}',

7
themes/beauty/controller/ListController.php

@ -5,6 +5,7 @@
require_once __DIR__ . '/../../../lib/DirScanner.php'; require_once __DIR__ . '/../../../lib/DirScanner.php';
require_once __DIR__ . '/../../../plugins/Parsedown.php'; require_once __DIR__ . '/../../../plugins/Parsedown.php';
require_once __DIR__ . '/../../../plugins/Common.php'; require_once __DIR__ . '/../../../plugins/Common.php';
require_once __DIR__ . '/../../../plugins/Html.php';
Class ListController extends Controller { Class ListController extends Controller {
@ -215,6 +216,12 @@ Class ListController extends Controller {
} }
if (!empty($item['extension']) && in_array($item['extension'], $videoExts)) { 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); array_push($videos, $item);
$index ++; $index ++;
} }

116
themes/beauty/controller/M3u8Controller.php

@ -0,0 +1,116 @@
<?php
/**
* M3u8 Controller
*/
require_once __DIR__ . '/../../../lib/DirScanner.php';
require_once __DIR__ . '/../../../plugins/Parsedown.php';
require_once __DIR__ . '/../../../plugins/Common.php';
Class M3u8Controller extends Controller {
//参数
//@id - 文件id
//@cid - 数据缓存id
//支持nginx secure防盗链:md5={$md5}&expires={$expires}
public function actionIndex() {
$videoId = $this->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}";
}
}

5
themes/beauty/controller/SiteController.php

@ -581,9 +581,12 @@ Class SiteController extends Controller {
//增加文件后缀格式检查,区分:mp4, mov, m3u8 //增加文件后缀格式检查,区分:mp4, mov, m3u8
$videoExtension = pathinfo($arr['path'], PATHINFO_EXTENSION); $videoExtension = pathinfo($arr['path'], PATHINFO_EXTENSION);
//支持m3u8地址:/m3u8/?id=xxx
if ($videoFilename == 'm3u8') {
$videoExtension = 'm3u8';
}
$videoSourceType = Html::getMediaSourceType($videoExtension); $videoSourceType = Html::getMediaSourceType($videoExtension);
//获取联系方式 //获取联系方式
$maxScanDeep = 0; //最大扫描目录级数 $maxScanDeep = 0; //最大扫描目录级数
$cacheKey = $this->getCacheKey('root', 'readme', $maxScanDeep); $cacheKey = $this->getCacheKey('root', 'readme', $maxScanDeep);

8
themes/beauty/views/site/index.php

@ -370,7 +370,13 @@ eof;
</div> </div>
eof; eof;
}else if (in_array($file['extension'], $videoExts)) { //输出视频 }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}"; $linkUrl = "/site/player?id={$file['id']}&pid={$file['pid']}&cid={$viewData['cacheDataId']}&url={$videoUrl}";
if ($viewData['showType'] == 'video') { if ($viewData['showType'] == 'video') {
$linkUrl .= "&page={$viewData['page']}&limit={$viewData['pageSize']}"; $linkUrl .= "&page={$viewData['page']}&limit={$viewData['pageSize']}";

5
themes/beauty/views/site/player.php

@ -36,9 +36,10 @@
class="video-js vjs-big-play-centered vjs-fluid vjs-16-9" class="video-js vjs-big-play-centered vjs-fluid vjs-16-9"
controls controls
playsinline playsinline
poster="" poster=""
data-src="<?php echo $viewData['videoUrl']; ?>"
data-type="<?php echo $viewData['videoSourceType']; ?>"
id="my-player"> id="my-player">
<source src="<?php echo $viewData['videoUrl']; ?>" type="<?php echo $viewData['videoSourceType']; ?>">
</video> </video>
<div class="text-right mt-2 mr-1"> <div class="text-right mt-2 mr-1">
<?php if (!empty($viewData['isAdminIp'])) { ?> <?php if (!empty($viewData['isAdminIp'])) { ?>

18
www/js/beauty.js

@ -610,6 +610,18 @@ if ($('#my-player').length > 0 && typeof(videojs) != 'undefined') {
setTimeout(takeScreenshot, screenshot_start); 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) { $('.btn-snapshot').click(function(e) {
var clickedBtn = $(e.target); var clickedBtn = $(e.target);
@ -719,7 +731,11 @@ if ($('#my-player').length > 0 && typeof(videojs) != 'undefined') {
} }
if (nextVideo) { if (nextVideo) {
myPlayer.src(nextVideo.path); //TODO: 支持其它格式,fix /m3u8/这种路径
myPlayer.src({
src: nextVideo.path,
type: nextVideo.videoType
});
$('.navbar-header .videotitle').text(nextVideo.filename); $('.navbar-header .videotitle').text(nextVideo.filename);
} }
}; };

Loading…
Cancel
Save