1.HTML5ビデオの再生
通常、ページで再生する動画はvideo
タグを介して表示され、src属性に表示する必要のある動画アドレスが追加されます。ブラウザは、幅、高さ、自動的に再生するかどうか、ループするかどうかなど、それに応じて属性を設定します。その他の属性、ブラウザのデフォルトのビデオコントロールを介してビデオを再生します。
<video controls width="250">
<source src="/media/cc0-videos/flower.webm" type="video/webm">
<source src="/media/cc0-videos/flower.mp4" type="video/mp4">
Sorry, your browser doesn't support embedded videos.
</video>
复制代码
2.一般的なイベントとプロパティ
一般的な実装:
属性 | 説明 |
---|---|
自動再生 | このプロパティを指定すると、データが読み込まれるのを待たずに直接再生されます |
緩衝 | メディアがキャッシュされている時間範囲を読み取ることができます |
コントロール | ユーザーが音量、一時停止、再開などのビデオの再生を制御できるようにします。 |
コントロールリスト | コントロールが指定されている場合、controlslistは、ブラウザーがメディア要素の表示コントロールを選択するのに役立ちます(パラメーターの受信:nodownload、nofullscreen ....) |
現在の時刻 | 値を設定すると、ビデオは現在の再生の開始時刻に値を設定します |
音量 | オーディオボリュームの相対値を0.0〜1.0に設定するか、現在のボリュームの相対値を照会します |
ミュート | ファイルをミュートするか、ミュートするか、ミュートを解除するか |
始まる時間 | 通常は0です。ストリーミングメディアまたは0から始まらないリソースの場合、0ではありません。 |
間隔 | 唯一読み取られるメディアファイルの全長。一部のメディア(不明なライブストリーム、Webキャスト、WebRTCのメディアなど)にはこの値がなく、NANを返します |
一時停止 | 読み取りのみが一時停止されているかどうか |
終了しました | のみ-オーディオ/ビデオの再生が終了したかどうかを読み取ります |
高さ/幅 | cssピクセル単位のビデオの高さ/幅 |
ループ | 指定すると、動画が最後に到達すると、動画が開始した場所に自動的に戻ります |
ポスター | ビデオカバー、再生中に画像が表示されない |
プリロード | none:事前にビデオをキャッシュしないでください。メタデータ:ソースデータを合理的にフェッチします。自動:このビデオを最初にロードする必要があります |
src | 動画を埋め込むためのURL |
一般的なイベント:
イベント | トリガーのタイミング |
---|---|
ロードスタート | ロードを開始します |
期間の変更 | durationプロパティ値が変更されたときにトリガーされます |
レートチェンジ | 再生速度が変化したときに発生します |
求める | seeking 寻找中 点击一个为(缓存)下载的区域 |
seeked | seeked 寻找完成时触发 |
play | 开始播放时触发 |
waiting | 播放由于下一帧数据未获取到导致播放停止,但是播放器没有主动预期其停止,仍然在努力的获取数据,简单的说就是在等待下一帧视频数据,暂时还无法播放。 |
playing | 我们能看到视频时触发,也就是真正处于播放状态 |
canplay | 浏览器可以播放媒体文件,但是没有足够的数据支撑到播放结束,需要不停缓存更多内容 |
pause | 暂停播放时触发 |
ended | 视频停止,media已经播放到终点时触发, loop 的情况下不会触发 |
volumechange | 音量改变时触发 |
loadedmetadata | 获取视频meta信息完毕,这个时候播放器已经获取到了视频时长和视频资源的文件大小。 |
loadeddata | media中的首帧已经加载时触发, 视频播放器第一次完成了当前播放位置的视频渲染。 |
abort | 客户端主动终止下载(不是因为错误引起) |
error | video.error.code: 1.用户终止 2.网络错误 3.解码错误 4.URL无效 |
canplaythrough | 浏览器可以播放文件,不需要停止缓存更多内容 |
progress | 客户端请求数据 |
timeupdate | 当video.currentTime发生改变时触发该事件 |
stalled | 网速失速 |
suspend | 延迟下载 |
方法:
方法 | 描述 |
---|---|
play() | 播放视频 |
pause() | 暂停视频 |
canPlayType() | 测试video元素是否支持给定MIME类型的文件 |
requestFullscreen() / mozRequestFullScreen() / webkitRequestFullScreen() | 全屏 |
3. 自定义视频播放器
首先需要去掉video
身上的属性controls
属性,将所有播放的动作交由我们自己控制。
3.1 自定义播放或暂停
const playBtn = document.getElementById('playBtnId');
playBtn.addEventListener('click', function() {
if (video.paused) {
video.play();
playBtn.textContent = '||'; // 切换样式
} else {
video.pause();
playBtn.textContent = '>'; // 切换样式
}
});
复制代码
3.2 音量控制
// 音量增加
const volIncBtn = document.getElementById('volIncId');
volIncBtn.addEventListener('click', function() {
video.volume > 0.9 ? (video.volume = 1) : (video.volume += 0.1);
});
// 音量减小
const volDecBtn = document.getElementById('volDecId');
volDecBtn.addEventListener('click', function() {
video.volume < 0.1 ? (video.volume = 0) : (video.volume -= 0.1);
});
复制代码
3.3 静音
const mutedBtn = document.getElementById('mutedId');
mutedBtn.addEventListener('click', function() {
video.muted = !video.muted;
mutedBtn.textContent = video.muted ? '恢复' : '静音';
});
复制代码
3.4 播放快进/快退
- 快进
const speedUpBtn = document.getElementById(speedUpId);
let _speed = 1;
speedUpBtn.addEventListener('click', function() {
_speed = _speed * 2;
if (_speed > 4) {
_speed = 1;
}
video.playbackRate = _speed;
speedUpBtn.textContent = _speed === 1 ? '快进' : '快进x' + _speed;
});
复制代码
- 快退
const backBtn = document.getElementById(backBtnId);
let back_speed = 1;
let _t;
backBtn.addEventListener('click', function() {
back_speed = back_speed * 2;
if (back_speed > 4) {
video.playbackRate = 1;
back_speed = 1;
clearInterval(_t);
} else {
video.playbackRate = 0;
clearInterval(_t);
_t = setInterval(function() {
video.currentTime -= back_speed * 0.1;
}, 100);
}
backBtn.textContent = back_speed === 1 ? '快退' : '快退x' + back_speed;
});
复制代码
3.5 全屏
const fullScreenBtn = document.getElementById(fullScreenId);
const fullScreen = function() {
fullScreenBtn.addEventListener('click', function() {
if (video.requestFullscreen) {
video.requestFullscreen();
} else if (video.mozRequestFullScreen) {
video.mozRequestFullScreen();
} else if (video.webkitRequestFullScreen) {
video.webkitRequestFullScreen();
}
});
};
复制代码
3.6 进度条和时间显示
const getTime = function() {
// 当前播放时间
nowTime.textContent = 0;
// 总时长
duration.textContent = 0;
video.addEventListener('timeupdate', function() {
// 当前播放时间, parseTime: 格式化时间
nowTime.textContent = parseTime(video.currentTime);
// 计算进度条
const percent = video.currentTime / video.duration;
playProgress.style.width = percent * progressWrap.offsetWidth + 'px';
});
video.addEventListener('loadedmetadata', function() {
// 更新视频总时长
duration.textContent = parseTime(video.duration);
});
};
复制代码
3.7プログレスバーを手動でクリックして早送りします(ビデオジャンプ)
progressWrap.addEventListener('click', function(e) {
if (video.paused || video.ended) {
video.play();
}
const length = e.pageX - progressWrap.offsetLeft;
const percent = length / progressWrap.offsetWidth;
playProgress.style.width = percent * progressWrap.offsetWidth + 'px';
video.currentTime = percent * video.duration;
});
复制代码
4.ビデオセグメントの読み込み
HTML5ビデオプレーヤーに関する上記の情報、次に、サーバーからクライアントにビデオを取得する必要があります。
ビデオファイルが大きい場合は、ストリーミングビデオをお勧めします。これは、任意のサイズをサポートします。を活用fs.createReadStream()
することで、サーバーはファイル全体を一度にメモリに読み込むのではなく、ストリーム内のファイルを読み取ることができます。次に、範囲リクエストを介してビデオをクライアントに送信します。また、クライアントはページがサーバーからビデオ全体をダウンロードするのを待つ必要がなく、ビデオが開始する数秒前にサーバーに要求でき、要求中にビデオを再生できます。
- fs.statSync():このメソッドは、ファイルの統計情報を取得するために使用されます。現在ロードされているチャンクがファイルの最後に達したときにファイルサイズを取得できます。
fileSize = fs.statSync(filePath).size
- fs.createReadStream():指定されたファイルのストリームを作成しますfs.createReadStream(filePath、{start、end})
- チャンク全体のサイズを返します:endChunk-startChunk。
- HTTP 206:データのブロックを中断することなくフロントエンドに提供するために使用されます。再リクエストする際には、次の情報が必要です。
- 'Content-Range':'バイトchunkStart-chunkEnd/ branchSize'
- 'Accept-Ranges':'バイト'
- 'Content-Length':chunkSize
- 'Content-Type':'video / webm'
ここでは、eggフレームワークを使用して、範囲要求ビデオの機能を実装します。
async getVideo() {
const { ctx } = this;
const req = ctx.request;
try {
const homedir = `${process.env.HOME || process.env.USERPROFILE}/`;
const filePath = path.resolve(`${process.env.NODE_ENV === 'development' ? '' : homedir}${req.query.filePath}`);
const range = req.headers.range;
const fileSize = fs.statSync(filePath).size;
if (range) {
const positions = range.replace(/bytes=/, '').split('-');
const start = parseInt(positions[0], 10);
const end = positions[1] ? parseInt(positions[1], 10) : fileSize - 1;
const chunksize = end - start + 1;
if (start >= fileSize) {
ctx.status = 416;
ctx.body =
'Requested range not satisfiable\n' + start + ' >= ' + fileSize;
return;
}
ctx.status = 206;
const header = {
'Accept-Ranges': 'bytes',
'Content-Type': 'video/webm',
'Content-Length': chunksize,
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'cache-control': 'public,max-age=31536000',
};
ctx.set(header);
ctx.body = fs
.createReadStream(filePath, {
start,
end,
autoClose: true,
})
.on('err', err => {
console.log(`[Video Play]: ${req.url}, 'pip stream error`);
ctx.body = err;
ctx.status = 500;
});
} else {
this.ctx.set('Content-Length', fileSize);
this.ctx.set('Content-Type', 'video/webm');
this.ctx.status = 200;
this.ctx.body = fs.createReadStream(filePath);
}
} catch (err) {
console.log(err);
ctx.body = err;
ctx.status = 500;
}
}
复制代码