html Javascript プログラミング 楽器

Youtube動画の区間(A-B)リピートや再生速度変更のできるブックマークレットを作った

動画の好きな所だけリピートしたい

Youtubeの動画に合わせて楽器の練習や耳コピをしたり、語学とか講座系の動画で勉強しているときには、特定の部分だけを繰り返し再生(A-Bリピート)させたいことが良くあります。

残念ながらYouTube自体には全体リピートの機能しかありません。もしかするとA-Bリピートのできる機能拡張があるのかもしれませんが、なんとなく機能拡張はあまり増やしたくないのです。

以前、ローカルの動画ファイルをA-Bリピートできるプレーヤーを作ったことがあります。その応用でYouTube上でA-Bリピートのできるブックマークレットを作りました。

ちなみにYouTube専用ではありません。HTML5標準のvideoタグで動画が配置されていて、ページ上の動画ファイルがひとつだけのサイトならどこでも使えます。

開発時はMacのChromeで確認しました。safariだとなぜか動きませんでした。

 

コントロールはA、B、Nのショートカット

使い方

1.あらかじめブックマークレットのコードをブックマークのURLとして設定しておきます。デモページにあるリンクをブックマークバーにドラッグアンドドロップするのが簡単かもしれません。

2.任意のYoutube動画を再生します

3.リピートさせたい場所の先頭でAキーを押します

4.リピートさせたい場所の最後でBキーを押すと、Aの場所に戻ります。以後はA-B間を自動リピートします

 

その他の機能

Nキーを押すと自動リピートはオフになります。もう一度Nキーを押すと再びA-B間をリピートします

マウスドラッグなどで再生時間を変更してA-B間から外れると、自動リピートはオフになります。Nキーで再リピートできます。

余談ですがNキーのショートカットを最初はCキーにしていたのですが、Youtubeの字幕(Caption)のショートカットとカブるんですね。

Bより後ろの時間で新たにAを設定すると、Bは動画の終わりに変更されます

Aより前の時間でBを設定するとAは動画の最初に変更されます

 

画面上にフィードバックはありませんが、javascriptコンソールに動作状態を書き出しています。

 

どんなものか試せるようにデモページを作りました。

デモページ(github pages)

ソースコード(github)

ブックマークレットのコード(ブックマークのURLにコピペ)

 javascript
javascript:const videoElement=document.querySelector('video');let repeatTime_A=0;let repeatTime_B=videoElement.duration;let enable_loop=true;console.log('A-B repeat bookmarklet is on');document.addEventListener('keydown',function(e){const now=videoElement.currentTime;console.log(e.key);if(e.shiftKey||e.ctrlKey||e.metaKey||e.altKey||e.isComposing)return;switch(e.code){case'KeyA':repeatTime_A=now;if(now>repeatTime_B)repeatTime_B=videoElement.duration;enable_loop=true;console.log('repeat',repeatTime_A,repeatTime_B);break;case'KeyB':repeatTime_B=now<1?1:now;if(now<=repeatTime_A)repeatTime_A=0;enable_loop=true;console.log('repeat',repeatTime_A,repeatTime_B);break;case'KeyN':enable_loop=!enable_loop;console.log('repeat',enable_loop);break}});videoElement.addEventListener('seeking',(e)=>{const now=videoElement.currentTime;if(now>repeatTime_B||now<repeatTime_A){enable_loop=false;console.log('repeat is off')}else{enable_loop=true;console.log('repeat is on')}});videoElement.addEventListener('timeupdate',(e)=>{const now=videoElement.currentTime;if(enable_loop){if(now>repeatTime_B){videoElement.currentTime=repeatTime_A}else if(now<repeatTime_A){videoElement.currentTime=repeatTime_A}}});void(0);

 

ソースコード

const videoElement = document.querySelector("video");
let repeatTime_A = 0;
let repeatTime_B = videoElement.duration;
let enable_loop = true;

console.log("A-B repeat bookmarklet is on");

document.addEventListener("keydown", function (e) {
  const now = videoElement.currentTime;
  console.log(e.key);
  if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey || e.isComposing) return;
  switch (e.code) {
    case 'KeyA':
      repeatTime_A = now;
      if (now > repeatTime_B) repeatTime_B = videoElement.duration;
      enable_loop = true;
      console.log("repeat", repeatTime_A, repeatTime_B);
      break;
    case 'KeyB':
      repeatTime_B = now < 1 ? 1 : now;
      if (now <= repeatTime_A) repeatTime_A = 0;
      enable_loop = true;
      console.log("repeat", repeatTime_A, repeatTime_B);
      break;
    case 'KeyN':
      enable_loop = !enable_loop;
      console.log("repeat", enable_loop);
      break;
  }
});

videoElement.addEventListener("seeking", (e) => {
  const now = videoElement.currentTime;
  if (now > repeatTime_B || now < repeatTime_A) {
    enable_loop = false;
    console.log("repeat is off");
  } else {
    enable_loop = true;
    console.log("repeat is on");
  }
});

videoElement.addEventListener("timeupdate", (e) => {
  const now = videoElement.currentTime;
  if (enable_loop) {
    if (now > repeatTime_B) {
      videoElement.currentTime = repeatTime_A;
    } else if (now < repeatTime_A) {
      videoElement.currentTime = repeatTime_A;
    }
  }
});

 

 

 

ブックマークレットのショートカットは文字入力と干渉する

このブックマークレットではctrlなどの修飾キーを併用せずに、単純にA、B、Cを押すと機能します。

ショートカットはdocument.addEventListnerで設定しているのでページ上では常に反応します。

このため同じページの検索フィールドやコメント欄に文字を入力するとショートカットが働いてしまうことがあります。

ただ、検索フィールドに文字を入れる行為は、このページを離れることを意味しますし、A-Bリピートさせている動画にコメントを打つ人もあまりいないと思うので、実用上は問題ないでしょう。

ブックマークレットなのでリロードすれば外れるますし。

一応、修飾キーを使った他のショートカットとは干渉しないようにしてあります。

 

video要素がひとつしかないのでdocument.querySelector("video")で捕まえられる

このブックマークレットを作るときの最初の問題は、javascriptでどうやって動画の要素を補足するかでした。

はじめはvideo要素のidを調べようかと思いました。

しかしよく考えるとYoutubeの場合はvideo要素はひとつしかないので、document.querySelector("video") で簡単に捕まえられました。

const videoElement = document.querySelector("video");

あとはvideo要素のイベントやプロパティを使っていくだけです。

このブックマークレットでは以下のプロパティやイベントを使いました。(次章にある速度可変版も含む)

 

プロパティ

currentTime:現在の再生時間の取得と設定(秒)。float

duration:動画の長さの取得。秒。

paused:動画が止まっているかどうかの取得

playbackRate:再生速度の取得と設定(1.0で通常)

 

イベント

timeupdate:動画の再生時間が進んだとき。再生中は適当な間隔で繰り返し発生する。このイベントでcurrentTimeを取得し、B時間を過ぎたらAに戻すという作業をやっている

seeking:動画をシーク中(マウスドラッグなどで再生位置を変更中)。このイベント中にcurrentTimeを取得し、A-Bの間から抜けたらenable_loopというフラグをオフにする(リピートさせない)

 

その他、video/audio要素のイベントやプロパティ、メソッドについては以下のサイトが分かりやすかったです。

audio要素/video要素のプロパティ、メソッド、イベント一覧 _ GRAYCODE JavaScript

 

 

速度変更も追加したバージョン

再生速度の変更や数秒スキップ機能を追加したバージョンも作ってみました。デモページの下の方にあります。

私は以前から「Video Speed Controller」という機能拡張を使っているので速度変更機能は不要なのですが、試しにブックマークレットでも同様の機能を再現してみました。

ショートカットはVideo Speed Controllerと同じくSとDで速度変更、ZとXで数秒スキップ、Rでリセットです。

当然のことながら、この機能拡張の入ったChromeでブックマークレットを起動すると機能が二重に働いてしまいます。

 

ブックマークレットのコード(ブックマークのURLにコピペ)

 bookmarklet
javascript:const videoElement=document.querySelector('video');let repeatTime_A=0;let repeatTime_B=videoElement.duration;let enable_loop=true;console.log('A-B repeat bookmarklet is on');document.addEventListener('keydown',function(e){const now=videoElement.currentTime;console.log(e.key);if(e.shiftKey||e.ctrlKey||e.metaKey||e.altKey||e.isComposing)return;switch(e.code){case'KeyA':repeatTime_A=now;if(now>repeatTime_B)repeatTime_B=videoElement.duration;enable_loop=true;console.log('repeat',repeatTime_A,repeatTime_B);break;case'KeyB':repeatTime_B=now<1?1:now;if(now<=repeatTime_A)repeatTime_A=0;enable_loop=true;console.log('repeat',repeatTime_A,repeatTime_B);break;case'KeyN':enable_loop=!enable_loop;console.log('repeat',enable_loop);break;case'KeyD':videoElement.playbackRate+=0.1;console.log('playback rate',videoElement.playbackRate);break;case'KeyS':videoElement.playbackRate-=0.1;console.log('playback rate',videoElement.playbackRate);break;case'KeyR':videoElement.playbackRate=1;console.log('playback rate',videoElement.playbackRate);break;case'KeyX':videoElement.currentTime+=10;console.log('+10sec');break;case'KeyZ':videoElement.currentTime-=5;console.log('-5sec');break}});videoElement.addEventListener('seeking',(e)=>{const now=videoElement.currentTime;if(now>repeatTime_B||now<repeatTime_A){enable_loop=false;console.log('repeat is off')}else{enable_loop=true;console.log('repeat is on')}});videoElement.addEventListener('timeupdate',(e)=>{const now=videoElement.currentTime;if(enable_loop){if(now>repeatTime_B){videoElement.currentTime=repeatTime_A}else if(now<repeatTime_A){videoElement.currentTime=repeatTime_A}}});void(0);

 

ソースコード

const videoElement = document.querySelector("video");
let repeatTime_A = 0;
let repeatTime_B = videoElement.duration;
let enable_loop = true;

console.log("A-B repeat bookmarklet is on");

document.addEventListener("keydown", function (e) {
  const now = videoElement.currentTime;
  console.log(e.key);
  if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey || e.isComposing) return;
  switch (e.code) {
    case 'KeyA':
      repeatTime_A = now;
      if (now > repeatTime_B) repeatTime_B = videoElement.duration;
      enable_loop = true;
      console.log("repeat", repeatTime_A, repeatTime_B);
      break;
    case 'KeyB':
      repeatTime_B = now < 1 ? 1 : now;
      if (now <= repeatTime_A) repeatTime_A = 0;
      enable_loop = true;
      console.log("repeat", repeatTime_A, repeatTime_B);
      break;
    case 'KeyN':
      enable_loop = !enable_loop;
      console.log("repeat", enable_loop);
      break;
      
    case 'KeyD':
      videoElement.playbackRate += 0.1;
      console.log("playback rate", videoElement.playbackRate);
      break;
    case 'KeyS':
      videoElement.playbackRate -= 0.1;
      console.log("playback rate", videoElement.playbackRate);
      break;
    case 'KeyR':
      videoElement.playbackRate = 1;
      console.log("playback rate", videoElement.playbackRate);
      break;
    case 'KeyX':
      videoElement.currentTime += 10;
      console.log("+10sec");
      break;
    case 'KeyZ':
      videoElement.currentTime -= 5;
      console.log("-5sec");
      break;
  }
});

videoElement.addEventListener("seeking", (e) => {
  const now = videoElement.currentTime;
  if (now > repeatTime_B || now < repeatTime_A) {
    enable_loop = false;
    console.log("repeat is off");
  } else {
    enable_loop = true;
    console.log("repeat is on");
  }
});

videoElement.addEventListener("timeupdate", (e) => {
  const now = videoElement.currentTime;
  if (enable_loop) {
    if (now > repeatTime_B) {
      videoElement.currentTime = repeatTime_A;
    } else if (now < repeatTime_A) {
      videoElement.currentTime = repeatTime_A;
    }
  }
});

 

ただし、Video Speed ControllerのほうがUIもちゃんとしているし、文字入力の影響も受けません。

ソースコードも公開されています。

Video Speed Controller(github)

 

 

 

-html, Javascript, プログラミング, 楽器