关于audio标签使用动态生成的mp3无法拖拽进度条的问题
html5 audio video不需要插件即可播放音频和视频,currentTime可以用来设置播放的起始时间,使得可以不从文件开头开始播放。如果 html5 audio video设置currentTime失效,无法设置开始播放的时间点,audio/video不能拖动进度条调整播放进度,很有可能是使用了php asp jsp等服务器后端语言动态输出待播放的媒体文件内容,如果audio/video播放的媒体资源文件不是静态文件,不经过处理直接通过服务器语言动态输出流媒体内容,设置currentTime不会生效。
为什么直接使用静态的.mp3 .mp4 .flac等媒体文件时支持使用 audio | video .currentTime = NUMBERIC; (NUMBERIC是一个数字,可以是整数,也可以是浮点数)来设置播放起始位置呢,而动态输出流媒体文件内容时不行呢?
这其中的核心技术就是服务器端对断点续传的支持,断点续传允许客户端从服务器提取某个文件指定字节范围内的一部分内容,当下载中断以后再次下载时可以只请求下载原先没有下载的部分,避免重复传输现有内容。当客户端(浏览器)检测到服务器支持断点续传以后才会发送相应的区间内容请求给服务器,服务器接到请求以后再返回相应范围内的文件内容,这样才能实现流媒体文件的定点播放。而直接使用静态文件做播放资源时,服务器软件通常会自动处理断点续传请求。
实现动态流媒体文件支持通过HTML5 audio/video.currentTime设置播放起始时间点,可以使用以下两种方法。
方法一:
使用服务器模块X-SendFile输出流媒体文件内容来解决currentTime失效的问题,以Apache服务器为例。
首先下载mod_xsendfile,将模块文件复制到Apache的modules目录,在Apache配置文件中添加
LoadModule xsendfile_module modules/mod_xsendfile.so
XSendFile On
XSendFilePath D:/server
复制代码其中XSendFilePath意思是将指定的文件夹路径添加到白名单中,指定文件夹中的文件可以被Apache直接发送给客户端,除了设置的文件夹本身以外,该文件夹包含的各级子目录也在允许范围内。配置完服务器以后重启Apache服务器以后X-SendFile模块有效。
安装了X-SendFile模块以后,PHP输出媒体文件变得非常简单。其中的Content-Type是要输出文件的MIME类型,可以不必写死而通过服务器语言获取。可以参见方法二有实例代码。
方法二:
使用服务器语言模拟断点续传支持让HTML5 audio/video.currentTime生效,以PHP为例。
如果没有服务器配置权限,可以在动态语言中发送Accept-Ranges: bytes和Content-Range: bytes S-E/FILESIZE两个响应头,告诉客户端它请求的服务器资源支持断点续传,服务器端接收到客户端发送的请求以后,从请求头拿到需要发送的内容区间,然后从文件中读取指定起止位置的数据发送到客户端即可解决HTML5 audio/video使用后端语言动态输出媒体文件内容造成的currentTime失效以及无法手动拖动进度条的问题。其中S和E分别是当次请求的起始位置索引和结束位置索引,FILESIZE为代表文件尺寸的总字节数,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<?php function downloadmp3($file) { $fileSize = filesize($file); $etag = md5(filemtime($file)); $fp = fopen($file, 'rb'); if (!$fp) { die('Could not open file'); } $start = 0; $end = $fileSize - 1; if (isset($_SERVER['HTTP_RANGE']) && !empty($_SERVER['HTTP_RANGE'])) { //获取请求头中的Range字段 $range = explode('-', substr($_SERVER['HTTP_RANGE'], strlen('bytes='))); $start = $range[0]; if ($range[1] > 0) { $end = $range[1]; } //构造断点续传响应头 header('HTTP/1.1 206 Partial Content'); header('Status: 206'); header('Accept-Ranges: bytes'); header('Content-Range: bytes ' . $start. '-' . $end . '/' . $fileSize); header('Content-Length: ' . ($end - $start + 1)); } else { header('Content-Length: ' . $fileSize); } header('Content-Type: audio/mpeg'); header('Last-Modified: ' . date('D, d M Y H:i:s \G\M\T', filemtime($file))); header('ETag: "' . $etag . '"'); header('Expires: 0'); if ($start > 0) { fseek($fp, $start); //移动文件指针 } $bytesPosition = $start; while (!feof($fp) && $bytesPosition <= $end) { $chunk = 1024 * 1024 * 50; //每次读取50k if ($bytesPosition + $chunk > $end + 1) { $chunk = $end - $bytesPosition + 1; } $chunk = fread($fp, $chunk); if (!$chunk) { die('Could not read file'); } print($chunk); flush(); $bytesPosition += $chunk; } fclose($fp); } ?> |