Skip to content

Commit 7329336

Browse files
Fix infinite loading spinner in MJPEG streaming mode on Chrome (#1306)
Resolves tiny-pilot/tinypilot-pro#765. The issue slipped through via https://github.com/tiny-pilot/tinypilot/pull/1270/files, specifically due to the fact that we switched to using one permanent/persistent `MediaStream` instance for the `video.srcObject` attribute. An empty `MediaStream` instance (i.e., one that doesn’t contain tracks) apparently makes Chrome’s loading indicator (on the browser tab) spin infinitely. We can’t, however, observe this behaviour on Firefox, so it might be that this is just some weird Chrome-specific glitch that we need to account for. From a purely technical point of view, this behaviour doesn’t seem to make immediate sense, since it’s explicitly allowed for a [`MediaStream` instance to not contain any tracks](https://developer.mozilla.org/en-US/docs/web/api/mediastream/mediastream). So it’s not really clear what Chrome is waiting for. Code-wise, the lazy-initialization is a bit unfortunate, because it’s less robust and requires us to guard our methods with defensive checks. I’ve added a prominent comment to document the problem. We might be able to refactor this later and make the code nicer, but for now this should do it. @mtlynch I tagged you for review, due to potential urgency – feel obviously free to defer. Due to the complexity of the `<remote-screen>` component, [I isolated the issue via this branch](https://github.com/tiny-pilot/tinypilot/compare/infinite-spinning-demo?expand=1) to verify the root cause: https://user-images.githubusercontent.com/83721279/217886452-863b24e1-6801-447f-9ab0-cc3527105ea9.mov <a data-ca-tag href="https://codeapprove.com/pr/tiny-pilot/tinypilot/1306"><img src="https://codeapprove.com/external/github-tag-allbg.png" alt="Review on CodeApprove" /></a>
1 parent 43eb80c commit 7329336

File tree

1 file changed

+17
-2
lines changed

1 file changed

+17
-2
lines changed

app/templates/custom-elements/remote-screen.html

+17-2
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,13 @@
140140
image: this.shadowRoot.getElementById("mjpeg-output"),
141141
};
142142

143-
// Initialize the WebRTC media stream.
144-
this.elements.video.srcObject = new MediaStream();
143+
// Note that we can’t assign a persistent `MediaStream` object to the
144+
// `srcObject` attribute that we would retain over the lifecycle of
145+
// this class, because otherwise Chrome shows an infinite loading
146+
// spinner for the browser tab. Therefore, we need to initialize the
147+
// source object lazily whenever we want to access it, and clear it
148+
// when we don’t need it anymore.
149+
this.elements.video.srcObject = null;
145150

146151
this._addScreenEventListeners(this.elements.video);
147152
this._addScreenEventListeners(this.elements.image);
@@ -347,6 +352,11 @@
347352
*/
348353
async enableWebrtcStreamTrack(mediaStreamTrack) {
349354
const video = this.elements.video;
355+
if (!video.srcObject) {
356+
// Lazy-initialize the media stream. (See comment in
357+
// `connectedCallback`.)
358+
video.srcObject = new MediaStream();
359+
}
350360
const stream = video.srcObject;
351361

352362
// Ensure that the stream doesn't contain multiple tracks of the same
@@ -401,6 +411,9 @@
401411
*/
402412
disableWebrtcStreamTrack(mediaStreamTrack) {
403413
const video = this.elements.video;
414+
if (!video.srcObject) {
415+
return;
416+
}
404417
const stream = video.srcObject;
405418
stream.removeTrack(mediaStreamTrack);
406419
if (stream.getVideoTracks().length === 0) {
@@ -488,6 +501,8 @@
488501
}
489502
this.elements.image.src = "/stream?advance_headers=1";
490503
this.webrtcEnabled = false;
504+
// Clean up the media stream. (See comment in `connectedCallback`.)
505+
this.elements.video.srcObject = null;
491506
this.dispatchEvent(new VideoStreamingModeChangedEvent("MJPEG"));
492507
}
493508

0 commit comments

Comments
 (0)