Skip to content

Commit

Permalink
Merge pull request #325 from StephenMcConnel/BL-13906-ErrorMessageWhe…
Browse files Browse the repository at this point in the history
…nVideoFails

feat: display message when browser can't play video (BL-13906) (#325)
  • Loading branch information
andrew-polk authored Sep 24, 2024
2 parents 124f0f8 + 51934f8 commit 9c6fd10
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 38 deletions.
7 changes: 6 additions & 1 deletion src/bloom-player-core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,12 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
`replay-button-${pageNumber}-${index}`
);
if (!replayButton) {
if (
video.networkState === HTMLMediaElement.NETWORK_NO_SOURCE &&
video.readyState === HTMLMediaElement.HAVE_NOTHING
) {
return; // don't create replay button if video is bad
}
replayButton = document.createElement("div");
replayButton.setAttribute(
"id",
Expand Down Expand Up @@ -1054,7 +1060,6 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
replayButton
);
}

replayButton.style.position = "absolute";
parent.appendChild(replayButton);
replayButton!.style.display = "block";
Expand Down
4 changes: 4 additions & 0 deletions src/l10n/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,9 @@
"Button.IgnoreImageDescriptions": {
"message": "Ignore Image Descriptions",
"description": "tooltip for the button to disable reading image descriptions"
},
"Video.BadVideoMessage": {
"message": "Sorry, this video cannot be played in this browser.",
"description": "message displayed when a video is invalid or missing"
}
}
57 changes: 37 additions & 20 deletions src/narration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
// and video

import LiteEvent from "./event";
import { hideVideoError, showVideoError } from "./video";
// Note: trying to avoid other imports, as part of the process of moving this code to a module
// that can be shared with BloomDesktop.

Expand Down Expand Up @@ -1247,26 +1248,42 @@ export function playAllVideo(elements: HTMLVideoElement[], then: () => void) {
return;
}
const video = elements[0];
// Note: in Bloom Desktop, sometimes this event does not fire normally, even when the video is played to the end.
// I have not figured out why. It may be something to do with how we are trimming them.
// In Bloom Desktop, this is worked around by raising the ended event when we detect that it has paused past the end point
// in resetToStartAfterPlayingToEndPoint.
// In BloomPlayer,I don't think this is a problem. Videos are trimmed when published, so we always play to the
// real end (unless the user pauses). So one way or another, we should get the ended event.
video.addEventListener(
"ended",
() => {
playAllVideo(elements.slice(1), then);
},
{ once: true }
);
// Review: do we need to do something to let the rest of the world know about this?
setCurrentPlaybackMode(PlaybackMode.VideoPlaying);

const promise = video.play();
// If there is an error, try to continue with the next video.
promise?.catch(reason => {
console.error("Video play failed", reason);
this.playAllVideo(elements.slice(1), then);
});
if (
video.networkState === HTMLMediaElement.NETWORK_NO_SOURCE &&
video.readyState === HTMLMediaElement.HAVE_NOTHING
) {
showVideoError(video);
this.playAllVideo(elements.slice(1));
} else {
hideVideoError(video);
// Review: do we need to do something to let the rest of the world know about this?
setCurrentPlaybackMode(PlaybackMode.VideoPlaying);
const promise = video.play();
promise
.then(() => {
// The promise resolves when the video starts playing. We want to know when it ends.
// Note: in Bloom Desktop, sometimes this event does not fire normally, even when the video is
// played to the end. I have not figured out why. It may be something to do with how we are
// trimming the videos.
// In Bloom Desktop, this is worked around by raising the ended event when we detect that it has
// paused past the end point in resetToStartAfterPlayingToEndPoint.
// In BloomPlayer,I don't think this is a problem. Videos are trimmed when published, so we always
// play to the real end (unless the user pauses). So one way or another, we should get the ended
// event.
video.addEventListener(
"ended",
() => {
playAllVideo(elements.slice(1), then);
},
{ once: true }
);
})
.catch(reason => {
console.error("Video play failed", reason);
showVideoError(video);
this.playAllVideo(elements.slice(1), then);
});
}
}
83 changes: 66 additions & 17 deletions src/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
setCurrentPlaybackMode,
PlaybackMode
} from "./narration";
import { LocalizationManager } from "./l10n/localizationManager";

// class Video contains functionality to get videos to play properly in bloom-player

Expand All @@ -14,6 +15,15 @@ export interface IPageVideoComplete {
videos: HTMLVideoElement[];
}

const uiLang = LocalizationManager.getBloomUiLanguage();
const preferredUiLanguages = uiLang === "en" ? [uiLang] : [uiLang, "en"];

const badVideoMessage = LocalizationManager.getTranslation(
"Video.BadVideoMessage",
preferredUiLanguages,
"Sorry, this video cannot be played in this browser."
);

export class Video {
private currentPage: HTMLDivElement;
private currentVideoElement: HTMLVideoElement | undefined;
Expand Down Expand Up @@ -178,26 +188,65 @@ export class Video {
this.currentVideoElement = undefined;
return;
}

this.currentVideoElement = video;
video.addEventListener(
"ended",
() => {
this.reportVideoPlayed(
video.currentTime - this.currentVideoStartTime
);
this.playAllVideo(elements.slice(1));
},
{ once: true }
);
setCurrentPlaybackMode(PlaybackMode.VideoPlaying);
this.currentVideoStartTime = video.currentTime || 0;
const promise = video.play();

// If there is an error, try to continue with the next video.
promise?.catch(reason => {
console.error("Video play failed", reason);
if (
video.networkState === HTMLMediaElement.NETWORK_NO_SOURCE &&
video.readyState === HTMLMediaElement.HAVE_NOTHING
) {
showVideoError(video);
this.playAllVideo(elements.slice(1));
});
} else {
hideVideoError(video);
setCurrentPlaybackMode(PlaybackMode.VideoPlaying);
this.currentVideoStartTime = video.currentTime || 0;
const promise = video.play();
promise
.then(() => {
// The promise resolves when the video starts playing. We want to know when it ends.
video.addEventListener(
"ended",
() => {
this.reportVideoPlayed(
video.currentTime - this.currentVideoStartTime
);
this.playAllVideo(elements.slice(1));
},
{ once: true }
);
})
.catch(reason => {
console.error("Video play failed", reason);
showVideoError(video);
this.playAllVideo(elements.slice(1));
});
}
}
}

export function showVideoError(video: HTMLVideoElement): void {
const parent = video.parentElement;
if (parent) {
const divs = parent.getElementsByClassName("video-error-message");
if (divs.length === 0) {
const msgDiv = parent.ownerDocument.createElement("div");
msgDiv.className = "video-error-message normal-style";
msgDiv.textContent = badVideoMessage;
msgDiv.style.display = "block";
msgDiv.style.color = "white";
msgDiv.style.position = "absolute";
msgDiv.style.left = "10%";
msgDiv.style.top = "10%";
msgDiv.style.width = "80%";
parent.appendChild(msgDiv);
}
}
}
export function hideVideoError(video: HTMLVideoElement): void {
const parent = video.parentElement;
if (parent) {
const divs = parent.getElementsByClassName("video-error-message");
while (divs.length > 1) parent.removeChild(divs[0]);
}
}

0 comments on commit 9c6fd10

Please sign in to comment.