操作に慣れていなくても、最初は掛かっても(=このページを見ながら操作しても)
せいぜい2分
慣れれば10秒で済みます
1. 配信ページ上で F12 キーを押してDevToolsを表示させる
ブラウザには、DevToolsと呼ばれる開発者・エンジニア用のツールが内蔵されています。
配信ページ上で F12 キーを押す事でこのツールを起動(表示)します
2. 作業しやすいように Dock side を分離させておく
※ 一度分離させれば、この設定がブラウザ上で引き継がれるので、以降の分離作業は不要となります
起動されたDevToolsは、最初はブラウザの右端や下側にくっついて表示されていると思います
このままでは狭いので、見やすくするために 一度ブラウザのページからDevToolsウィンドウを分離させます
DevTools の右上にある三点リーダーのようなボタンを押してメニューを表示
そのメニュー内 上側にある「Dock side」項目の一番左側を押す事で、分離が可能です
3. Console を表示させる
DevTools上側を見ると、横並びになっている英語のメニュー項目があると思います(タブのような感覚)
左側から二番目にある「Console」をクリック選択し、機能を切り替えます。
( 既に最初から Console モードとして表示されている場合があるかもしれません )
4. Console の入力欄経由で任意のコードを実行
Console に切り替わった画面の一番下に、
青い 「>」 の様な記号が左端に表示された箇所が見つかる筈です
ここは入力欄になっており、ここにプログラムのコードを張り付けたりして、ページ上のデータにアクセス・操作が出来るようになっています
ここに最初の方に示した実行コードをコピーペーストなどで入力し、
入力後にそのまま Enter キーを押せば、その場でページ上で実行されます。
(↓コード再掲)
document.querySelectorAll("video").forEach(_e=>_e.muted=true);
実行すれば、重複して聞こえているように感じていた同時配信者の音声が、平時の状態に戻っている筈です。
用が済んだら、このDevToolsは閉じて構いません。
ただ、配信ページを開きなおしたり、配信参加者が増減するたびに再びダブって聞こえるようになると思います
その場合は、先のコードを再び実行して対応してください
5. (おまけ) 履歴を用いる
consoleで用意されている機能・特性の一つに「過去に実行したコードを記録している(覚えていてくれる)」というものがあります。
コードを入力した 青い 「>」 の箇所をクリックし、
文字を入力するモードになっているときに、キーボードの 「↑」 キーを押すと、1つ前..2つ前..と、過去実行したコードを再び表示(用意)してくれます
つまり、「実行した履歴(のコード)が再び入力用意されている状態」になりますので、
あとはエンターキーを押すだけで、過去実行したコードをコピペなどの手間なしに再び即座に実行できます
同時配信のたび、いちいち「どういうコードを実行すればいいんだっけ...」と、
態々このページを探してコピペする手間が無くなって便利です😋
視聴者・配信者同士 の如何に関わらず、視聴窓(雑に言えばvideoとか)のオーディオ構築関係に携わる処理の一端として、
/static/app/components/Stream/Stream.tsx の handleInitAudioProcess が呼ばれる模様。
this.handleInitAudioProcess = () => {
this.source = null;
this.audioContext && this.audioContext.close();
// eslint-disable-next-line @typescript-eslint/naming-convention
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (AudioContext) {
this.audioContext = new AudioContext();
this.processor = {
node: this.audioContext.createScriptProcessor(512),
clipping: false,
lastClip: 0,
volume: 0,
clipLevel: 0.98,
averaging: 0.95,
clipLag: 750,
checkClipping: this.handleAudioProcessCheckClipping,
shutdown: this.handleAudioProcessShutdown,
};
this.processor.node.onaudioprocess = this.handleAudioProcess;
this.processor.node.connect(this.audioContext.destination);
// ■自分(あなた自身)が配信者である場合
// ■this.props は MediaStream
if (this.props.performed) {
if (this.props.stream.getAudioTracks().length > 0) {
this.source = this.audioContext.createMediaStreamSource(this.props.stream);
// this.source.connect(this.processor.node);
}
}
// 自分が視聴者の場合
else {
this.source = this.audioContext.createMediaElementSource(this.video);
// this.source.connect(this.processor.node);
}
if (this.source) {
this.source.connect(this.processor.node);
this.gainNode = this.audioContext.createGain();
this.gainNode.gain.value = this.state.muted ? 0 : this.state.volume;
this.video.volume = this.state.muted ? 0 : this.state.volume;
this.source.connect(this.gainNode);
this.gainNode.connect(this.audioContext.destination);
}
else {
this.audioContext = null;
}
}
};
細かい原因まではつかみ切れていないうえ、オーディオ処理に関しては良く分からないものの、
構築された this.video (HTMLVideoElement) の .muted を true にする事で今回の重複現象が解消される事を体感として確認済み
後半 if (this.source){~ 内の処理は、ページ上のコントロールである、ミュートボタンやスライダーとの連動、ボリューム連動やその設定値復元などを担っている模様。
この中身は触らないほうが良いっぽい?
if (this.props.performed) 付近で、態々コメントアウトされて残っているコードが気になったが、そもそもオーディオ処理が分からないので未検証
↓ ちなみに this.source の中身は、配信者か視聴者かで微妙に異なる模様
this.source =
MediaStreamAudioSourceNode (配信者同士)
this.source =
MediaElementAudioSourceNode (視聴者側)
ただ、ぶら下がっているプロパティなどはほとんど同じで、共通一例だと
this.source.context = AudioContext
this.source.mediaStream = MediaStream
など。
MDN - AudioContext.createMediaStreamSource() のページ内にて提示されているコード中、
コールバック処理内に以下の記述があり、明示的に video 要素としてミュートされている所が気になった。
video.onloadedmetadata = function(e) {
video.play();
video.muted = 'true';
};
(ボリューム管理などの為に) 同時配信相手の音周りを Gain や AudioDestination に繋いで操作(+出力)させているのであれば、
video 属性のほうを敢えてミュートにしなければ、重複して再生されてしまう仕様なのでは?ともうっすら感じた。(確証はない)
stack overflow - Audio from RTCPeerConnection is not audible after processing in AudioContext
ただそれとは別に、
this.handleToggleMute 内の this.setState() 経由だと、ダブりを含めて一括ミュートになるので、
this.setState() に muted キーが与えられたときの挙動を細かく掘れれば、原因を別角度から切り分けられるかも?とうっすら感じた。
(現状、掘り下げてはいない)
未検証だが、(とある).componentDidUpdate の中で video のボリューム操作が為されている箇所があり、
ミュート時にこの処理で Gain と一括にまとめてミュート(厳密には video.volume = 0)されているっぽいから、
ミュートボタンを押すだけでダブり分も一括消音(というよりはボリュームが0になる)されるのかもしれない
とりあえずの変更案
this.handleInitAudioProcess = () => {
//...中略...
if (AudioContext) {
/*
...
中略
...
*/
if (this.props.performed) {
if (this.props.stream.getAudioTracks().length > 0) {
this.source = this.audioContext.createMediaStreamSource(this.props.stream);
// this.source.connect(this.processor.node);
}
}
else {
this.source = this.audioContext.createMediaElementSource(this.video);
// this.source.connect(this.processor.node);
}
if (this.source) {
this.source.connect(this.processor.node);
this.gainNode = this.audioContext.createGain();
this.gainNode.gain.value = this.state.muted ? 0 : this.state.volume;
this.video.volume = this.state.muted ? 0 : this.state.volume;
this.source.connect(this.gainNode);
this.gainNode.connect(this.audioContext.destination);
//▼+追加ここから
if(r.props.performed){
// 配信者同士の場合のみ、video を明示的に消音(volume 0 だと、多分後で復活させられてしまう気がする)
r.video.muted = true;
}
//▲+追加ここまで
}
else {
this.audioContext = null;
}
}
};
検証環境
2021/05/22前後
GoogleChrome 90.0.4430.212(Official Build)x64
FireFox..?知らない子ですね..
当該スクリプトファイル名 : /static/20.1718b3ae.chunk.js
( /static/app/components/Stream/Stream.tsx 相当? )
検証アプローチメモの名残
{
// これは完全に除去される
const _at = document.querySelectorAll("video")[1].srcObject.getAudioTracks()[0];
document.querySelectorAll("video")[1].srcObject.removeTrack(_at);
}
{
document.querySelectorAll("video")[1].muted = true;
//または以下でも"止まる"(厳密に言えばミュートとは別ですしおすし)
document.querySelectorAll("video")[1].srcObject.getAudioTracks()[0].stop();
}
配列[0]は自分。以降は同時配信者