Skip to content

Commit 038f2b8

Browse files
authored
Merge pull request #1 from miya-biz/feature/sorasfu
Fix an issue that the stream does not start properly on the remote side when the video is turned off
2 parents 0ec02d5 + c500ffb commit 038f2b8

File tree

1 file changed

+53
-4
lines changed

1 file changed

+53
-4
lines changed

static/js/index.js

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class LocalTracks extends EventTargetPolyfill {
6565
super();
6666
Object.defineProperty(this, 'stream', {value: new MediaStream(), writeable: false});
6767
this.videoIsScreenshare = false;
68+
this.videoIsDummy = false;
6869
}
6970

7071
getTracks(kind) {
@@ -224,6 +225,10 @@ exports.rtc = new class {
224225
this._selfViewButtons = {};
225226
// When grabbing both locks the audio lock must be grabbed first to avoid deadlock.
226227
this._trackLocks = {audio: new Mutex(), video: new Mutex()};
228+
// for dummy canvas stream
229+
this._dummyCanvasStream = null;
230+
this._dummyCanvas = null;
231+
this._dummyCanvasTimerId = null;
227232
}
228233

229234
get enableDebugLogging() { return enableDebugLogging; }
@@ -328,7 +333,7 @@ exports.rtc = new class {
328333
handleClientMessage_RTC_MESSAGE(hookName, {payload: {from, data}}) {
329334
debug(`(peer ${from}) received message`, data);
330335
const {publish, clientId, needsReply} = data;
331-
if (publish != null && clientId != null) {
336+
if (this._activated && publish != null && clientId != null) {
332337
if (from !== this.getUserId()) {
333338
debug(`*add cliend id ${clientId} to user id ${from}`);
334339
this._clientIdToUserId.set(clientId, from);
@@ -493,7 +498,7 @@ exports.rtc = new class {
493498
if (addAudioTrack) devices.push('mic');
494499
const addVideoTrack = updateVideo && this._selfViewButtons.video.enabled &&
495500
(!this._localTracks.stream.getVideoTracks().some((t) => t.readyState === 'live') ||
496-
this._localTracks.videoIsScreenshare);
501+
this._localTracks.videoIsScreenshare || this._localTracks.videoIsDummy);
497502
if (addVideoTrack) devices.push('cam');
498503
const addScreenshareTrack = updateVideo && this._selfViewButtons.screenshare.enabled &&
499504
!this._selfViewButtons.video.enabled && // Video button overrides screenshare button.
@@ -534,6 +539,9 @@ exports.rtc = new class {
534539
for (const track of stream.getTracks()) {
535540
if (track.kind === 'video') {
536541
this._localTracks.videoIsScreenshare = devices.includes('screen');
542+
this._localTracks.videoIsDummy = false;
543+
// dummy video stream is no longer needed
544+
this.disposeDummyCanvasStream();
537545
}
538546
this._localTracks.setTrack(track.kind, track);
539547
}
@@ -565,11 +573,51 @@ exports.rtc = new class {
565573
if (updateVideo) this._trackLocks.video.unlock();
566574
if (updateAudio) this._trackLocks.audio.unlock();
567575
}
576+
// For some browsers, audio stream is not played without video stream,
577+
// so add video stream with dummy canvas.
578+
await this._trackLocks.video.lock();
579+
if (this._localTracks.stream.getVideoTracks().length === 0) {
580+
const dummyVideoTrack = this.createDummyCanvasStream().getVideoTracks()[0];
581+
this._localTracks.setTrack(dummyVideoTrack.kind, dummyVideoTrack);
582+
this._localTracks.videoIsDummy = true;
583+
}
584+
this._trackLocks.video.unlock();
568585
// For most browsers, autoplay with audio is allowed if the user grants access to the camera or
569586
// microphone, so unmuting auto-muted videos is likely to succeed.
570587
await this.unmuteAndPlayAll();
571588
}
572589

590+
createDummyCanvasStream() {
591+
if (!this._dummyCanvasStream) {
592+
this._dummyCanvas = document.createElement('canvas');
593+
const canvasWidth = 160;
594+
const canvasHeight = 120;
595+
this._dummyCanvas.width = canvasWidth;
596+
this._dummyCanvas.height = canvasHeight;
597+
const ctx = this._dummyCanvas.getContext('2d');
598+
ctx.fillStyle = '#000000';
599+
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
600+
this._dummyCanvasTimerId = setInterval(() => {
601+
if (this._dummyCanvas) {
602+
const ctx = this._dummyCanvas.getContext('2d');
603+
ctx.fillStyle = '#000000';
604+
ctx.fillRect(0, 0, this._dummyCanvas.width, this._dummyCanvas.height);
605+
}
606+
}, 1000);
607+
this._dummyCanvasStream = this._dummyCanvas.captureStream(1);
608+
}
609+
return this._dummyCanvasStream;
610+
}
611+
612+
disposeDummyCanvasStream() {
613+
if (this._dummyCanvasTimerId) {
614+
clearInterval(this._dummyCanvasTimerId);
615+
this._dummyCanvasTimerId = null;
616+
}
617+
this._dummyCanvasStream = null;
618+
this._dummyCanvas = null;
619+
}
620+
573621
async activate() {
574622
if (!this._activated) {
575623
this._activated = (async () => {
@@ -640,6 +688,7 @@ exports.rtc = new class {
640688
for (const track of this._localTracks.stream.getTracks()) {
641689
this._localTracks.setTrack(track.kind, null);
642690
}
691+
this.disposeDummyCanvasStream();
643692
} finally {
644693
this._trackLocks.video.unlock();
645694
this._trackLocks.audio.unlock();
@@ -673,8 +722,8 @@ exports.rtc = new class {
673722
}
674723

675724
async playVideo($video) {
676-
// play() will block indefinitely if there are no enabled tracks.
677-
if (!$video[0].srcObject.getTracks().some((t) => t.enabled)) return;
725+
// play() will block indefinitely if there are no enabled 'video' tracks.
726+
if (!$video[0].srcObject.getVideoTracks().some((t) => t.enabled)) return;
678727
debug('playing video', $video[0]);
679728
const $videoContainer = $(`#container_${$video[0].id}`);
680729
const $playErrorBtn = $videoContainer.find('.play-error-btn');

0 commit comments

Comments
 (0)