Node.jsとHTML5ビデオストリーミングを備えたカスタムプレーヤー

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:データのブロックを中断することなくフロントエンドに提供するために使用されます。再リクエストする際には、次の情報が必要です。
    1. 'Content-Range':'バイトchunkStart-chunkEnd/ branchSize'
    2. 'Accept-Ranges':'バイト'
    3. 'Content-Length':chunkSize
    4. '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;
    }
  }
复制代码

支配:Node.jsとHTML5を使用したビデオストリーム

おすすめ

転載: juejin.im/post/7005113621415985183