#私が考えた最強の方法
##MediaElement.captureStream()
MediaElementにはcaptureStream()という機能があり、MediaElementから直接MediaStreamを取得することができるメソッドです。
<canvas>にはすでにcaptureStream()がサポートされているのですが、まだ<video>や<audio>はサポートされていません。
ただ、現在はデフォルトで無効になっているという状態で、chrome://flags の"試験運用版のウェブ プラットフォームの機能"(chrome://flags/#enable-experimental-web-platform-features)を有効に設定することで<video>や<audio>でも使用できるようになります。
##拡張機能
Youtubeの埋め込みプレイヤーはiframeで埋め込まれるため、コンテンツ(親フレーム)のJSからプレイヤーの<video>にアクセスすることができないため、拡張機能を使用してアクセスします。
拡張機能および、captureStream()の機能を使用することで、Youtube埋め込みプレイヤーから直接Video/Audioストリームを取得することができるようになります。
##公開するには
ブラウザーでcaputureStream()がデフォルトで有効になるバージョンになるまで待てればそれで構いませんし、またユーザーに実験中の機能を有効にしてもらうというのが嫌でなければそのまま公開してもいいでしょう。
ユーザーに有効にしてもらうのが嫌というのでしたら、Origin Trialsという仕組みがあります。これは、Originと有効に設定するフラグを登録することで、ユーザーの設定作業無しに、そのOriginにアクセスすると自動的に登録した実験中の機能が有効になるというものです。
詳しくは、Web 標準化のフィードバックサイクルを円滑にする Origin Trials についてを見てください。
##サンプルコード
####コンテンツページ
index.html
HTML
1<!DOCTYPE html>
2<html>
3<head>
4 <script src="three.min.js"></script>
5 <script src="OrbitControls.js"></script>
6 <style>
7 html, body {
8 width: 100%;
9 height: 100%;
10 margin: 0;
11 }
12
13 iframe {
14 width: 360px;
15 height: 200px;
16 left: 0;
17 top: 0;
18 position: absolute;
19 z-index: 1;
20 }
21 </style>
22</head>
23<body>
24 <div id="player"></div>
25 <script src="app.js"></script>
26</body>
27</html>
app.js
js
1const extId = 'bbchnfmcdcokmipmngjjniflkoakncbl';
2chrome.runtime.sendMessage(extId, 'content tab handle');
3
4const tag = document.createElement('script');
5tag.src = "https://www.youtube.com/iframe_api";
6const firstScriptTag = document.getElementsByTagName('script')[0];
7firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
8let player;
9let inited = false;
10const vid = 'bHQqvYy5KYo';
11
12const renderer = new THREE.WebGLRenderer({
13 antialias: true
14});
15renderer.setSize(window.innerWidth, window.innerHeight);
16document.body.appendChild(renderer.domElement);
17const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
18camera.position.set(0, 0, 300);
19const controls = new THREE.OrbitControls(camera);
20const scene = new THREE.Scene();
21const obj = new THREE.Mesh();
22obj.material = new THREE.MeshBasicMaterial({
23 color: 0xffffff,
24 wireframe: true
25});
26obj.geometry = new THREE.TorusKnotGeometry(0, 0, 0, 0, 0, 0);
27scene.add(camera);
28scene.add(obj);
29
30window.addEventListener('resize', () => {
31 renderer.setSize(window.innerWidth, window.innerHeight);
32 camera.aspect = window.innerWidth / window.innerHeight;
33 camera.updateProjectionMatrix();
34})
35
36function onYouTubeIframeAPIReady() {
37 player = new YT.Player('player', {
38 height: 200,
39 width: 200,
40 videoId: vid,
41 events: {
42 onReady: onPlayerReady,
43 onStateChange: onPlayerStateChange
44 }
45 });
46}
47
48function onPlayerReady(event) {
49 event.target.playVideo();
50 DrawGraph();
51}
52
53function onPlayerStateChange(event) {
54 if (event.data === YT.PlayerState.PLAYING && !inited) {
55 chrome.runtime.sendMessage(extId, 'ready');
56 inited = true;
57 }
58}
59
60function stopVideo() {
61 player.stopVideo();
62}
63
64function DrawGraph() {
65 requestAnimationFrame(DrawGraph);
66 chrome.runtime.sendMessage(extId, 'get audio data', function(spectrums) {
67 obj.geometry.dispose();
68 obj.geometry = new THREE.TorusKnotGeometry( //小数をかけてるのは値を小さくして、3Dオブジェクトのサイズを小さくするため
69 Math.round(spectrums[0] * 2.0), //全体的な大きさ
70 Math.round(spectrums[1] * 0.3), //チューブの太さ
71 Math.round(40), //クネクネの進む方向に対してなん分割するか
72 Math.round(8), //チューブ方向に何分割するか
73 Math.round(spectrums[2] * 0.3), //なんかクネクネ具合が変わる数値その1
74 Math.round(2) //なんかクネクネ具合が変わる数値その2
75 );
76 });
77 controls.update();
78 renderer.render(scene, camera);
79}
####拡張機能
manifest.json
json
1{
2 "manifest_version": 2,
3 "version": "0.0.1",
4 "name": "埋め込みプレイヤーオーディオデータ取得テスト",
5 "content_scripts": [{
6 "matches": [
7 "https://www.youtube.com/embed/*"
8 ],
9 "js": ["iframe_content.js"],
10 "run_at": "document_start",
11 "all_frames": true
12 }],
13 "background": {
14 "scripts": ["background.js"]
15 },
16 "externally_connectable": {
17 "matches": [
18 "https://turbographics2000.github.io/get_youtube_audiodata/"
19 ]
20 }
21}
background.js
js
1let contentTab = null;
2chrome.runtime.onMessageExternal.addListener(handleMessage);
3chrome.runtime.onMessage.addListener(handleMessage)
4
5function handleMessage(msg, sender, res) {
6 console.log(msg);
7 switch (msg) {
8 case 'content tab handle':
9 contentTab = sender.tab.id;
10 console.log('contentTab', contentTab);
11 break;
12 case 'ready':
13 chrome.tabs.sendMessage(contentTab, 'ready');
14 break;
15 case 'get audio data':
16 if (!contentTab) return;
17 chrome.tabs.sendMessage(contentTab, msg, (data) => {
18 res(data);
19 });
20 break;
21 }
22 return true;
23}
iframe_content.js
js
1const audioctx = new AudioContext();
2const analyser = audioctx.createAnalyser();
3
4chrome.runtime.onMessage.addListener((msg, sender, res) => {
5 switch (msg) {
6 case 'ready':
7 let video = document.getElementsByTagName('video')[0];
8 let stream = video.captureStream();
9 let micsrc = audioctx.createMediaStreamSource(stream);
10 analyser.fftSize = 128;
11 analyser.minDecibels = -100;
12 analyser.maxDecibels = -30;
13 micsrc.connect(analyser);
14 break;
15 case 'get audio data':
16 let spectrums = new Uint8Array(analyser.frequencyBinCount);
17 analyser.getByteFrequencyData(spectrums);
18 res(spectrums);
19 break;
20 }
21});
##デモページ
デモページを用意しました。
"試験運用版のウェブ プラットフォームの機能"(chrome://flags/#enable-experimental-web-platform-features)を有効にし(ブラウザーの再起動)、サンプル拡張機能を先にインストールしてから、デモページにアクセスしてください。
##謝辞
サンプルコードおよびデモページで、質問のコードを使用させていただきました。
##最後に
確実にピンポイントなストリームを取得することができるというのが一番の利点ですが、見ての通り、単純にgetUserMedia()を使用するより、こちらのほうが実装においてははるかに回りくどいものとなっています。
あと、質問のコードではnavigator.getUserMedia()を使用しているようですが、これは古い仕様のものです。
navigator.mediaDevices.getUserMedia()を使用するようにしましょう(Promise)(prefix解決も不要です)。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/01/23 11:45