Skip to content

Commit

Permalink
fix: Play all videos on the page (BL-13705)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew-polk committed Jul 31, 2024
1 parent 19a747b commit 871ec47
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 167 deletions.
162 changes: 84 additions & 78 deletions src/bloom-player-core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1007,77 +1007,79 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
}

private showReplayButton(pageVideoData: IPageVideoComplete | undefined) {
if (!pageVideoData?.video || !pageVideoData!.page) {
return; // paranoia, and allows us to assume they are defined without ! everywhere.
}
const parent = pageVideoData.video.parentElement!;
let replayButton = document.getElementById("replay-button");
if (!replayButton) {
replayButton = document.createElement("div");
replayButton.setAttribute("id", "replay-button");
replayButton.style.position = "absolute";
replayButton.style.display = "none";
ReactDOM.render(
<Replay
style={{ backgroundColor: "rgba(255,255,255,0.5)" }}
onClick={args => {
// in storybook, I was seeing the page jump around as I clicked the button.
// Guessing it was somehow caused by something higher up also responding to
// the click, I put these in to try to stop it, but didn't succeed.
// If we get the behavior in production, we'll need to try some more.
args.preventDefault();
args.stopPropagation();
// This not only starts the video, it should put everything in the right
// state, including stopping any audio. If we change our minds about
// always playing video first, or decide to support more than one video
// on a page, we'll need something smarter here.
this.resetForNewPageAndPlay(
BloomPlayerCore.currentPage!
);
}}
onTouchStart={args => {
// This prevents the toolbar from toggling if we start a touch on the Replay button.
// If the touch ends up being a tap, then onClick will get processed too.
this.setState({ ignorePhonyClick: true });
}}
onMouseDown={args => {
// another attempt to stop the jumping around.
args.stopPropagation();
}}
/>,
replayButton
pageVideoData?.videos.forEach((video, index) => {
const parent = video.parentElement!;
let replayButton = document.getElementById(
`replay-button-${index}`
);
}
// from https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
// const UA = navigator.userAgent;
// const isWebkit =
// /\b(iPad|iPhone|iPod)\b/.test(UA) &&
// /WebKit/.test(UA) &&
// !/Edge/.test(UA) &&
// !window.MSStream;
//if (isWebkit || /chrome/.test(UA.toLowerCase())) {
// Due to a bug in Chrome and Safari (and probably other Webkit-based browsers),
// we can't be sure the button will show up if we place it over the video
// So, instead, we hide the video to make room for it. That can seem a very abrupt change,
// so we fade the video out before replacing it with the button.
// We have tried keeping the behavior in the 'else' commented out below, which we would
// prefer, for those browsers which can do it correctly; but it has proved too difficult
// to detect which those are.
parent.insertBefore(replayButton, pageVideoData.video); // there but still display:none
pageVideoData.video.classList.add("fade-out");
setTimeout(() => {
pageVideoData.video.style.display = "none";
replayButton!.style.display = "block";
pageVideoData.video.classList.remove("fade-out");
}, 1000);
// } else {
// // On correctly-implemented browsers, it's neater to just overlay the button on top of the video.
// // keeping this code in case we decide to try again sometime, and to remember what we'd like to
// // do, but it will likely be a long time before we can be sure no problem browsers are around any more.
// replayButton.style.position = "absolute";
// parent.appendChild(replayButton);
// replayButton!.style.display = "block";
// }
if (!replayButton) {
replayButton = document.createElement("div");
replayButton.setAttribute("id", `replay-button-${index}`);
replayButton.classList.add("replay-button");
replayButton.style.position = "absolute";
replayButton.style.display = "none";
ReactDOM.render(
<Replay
style={{ backgroundColor: "rgba(255,255,255,0.5)" }}
onClick={args => {
// in storybook, I was seeing the page jump around as I clicked the button.
// Guessing it was somehow caused by something higher up also responding to
// the click, I put these in to try to stop it, but didn't succeed.
// If we get the behavior in production, we'll need to try some more.
args.preventDefault();
args.stopPropagation();

video.style.display = "block";
if (replayButton)
// replayButton is always defined, but TS doesn't know that.
replayButton.style.display = "none";
this.video.replaySingleVideo(video);
}}
onTouchStart={args => {
// This prevents the toolbar from toggling if we start a touch on the Replay button.
// If the touch ends up being a tap, then onClick will get processed too.
this.setState({ ignorePhonyClick: true });
}}
onMouseDown={args => {
// another attempt to stop the jumping around.
args.stopPropagation();
}}
/>,
replayButton
);
}

// from https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
// const UA = navigator.userAgent;
// const isWebkit =
// /\b(iPad|iPhone|iPod)\b/.test(UA) &&
// /WebKit/.test(UA) &&
// !/Edge/.test(UA) &&
// !window.MSStream;
//if (isWebkit || /chrome/.test(UA.toLowerCase())) {
// Due to a bug in Chrome and Safari (and probably other Webkit-based browsers),
// we can't be sure the button will show up if we place it over the video
// So, instead, we hide the video to make room for it. That can seem a very abrupt change,
// so we fade the video out before replacing it with the button.
// We have tried keeping the behavior in the 'else' commented out below, which we would
// prefer, for those browsers which can do it correctly; but it has proved too difficult
// to detect which those are.
parent.insertBefore(replayButton, video); // there but still display:none
video.classList.add("fade-out");
setTimeout(() => {
video.style.display = "none";
replayButton!.style.display = "block";
video.classList.remove("fade-out");
}, 1000);
// } else {
// // On correctly-implemented browsers, it's neater to just overlay the button on top of the video.
// // keeping this code in case we decide to try again sometime, and to remember what we'd like to
// // do, but it will likely be a long time before we can be sure no problem browsers are around any more.
// replayButton.style.position = "absolute";
// parent.appendChild(replayButton);
// replayButton!.style.display = "block";
// }
});
}

// We need named functions for each LiteEvent handler, so that we can unsubscribe them
Expand Down Expand Up @@ -2768,20 +2770,24 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
}
player.sendUpdateOfBookProgressReportToExternalContext();
}

// This should only be called when NOT paused, because it will begin to play audio and highlighting
// and animation from the beginning of the page.
private resetForNewPageAndPlay(bloomPage: HTMLElement): void {
if (this.props.paused) {
return; // shouldn't call when paused
}
const replayButton = document.getElementById("replay-button");
if (replayButton) {
replayButton.style.display = "none";
const video = replayButton.parentElement?.getElementsByTagName(
"video"
)[0];
if (video) {
video.style.display = "";
const replayButtons = document.getElementsByClassName("replay-button");
if (replayButtons) {
for (let i = 0; i < replayButtons.length; i++) {
const replayButton = replayButtons[i] as HTMLElement;
replayButton.style.display = "none";
const video = replayButton.parentElement?.getElementsByTagName(
"video"
)[0];
if (video) {
video.style.display = "";
}
}
}
this.animation.PlayAnimation(); // get rid of classes that made it pause
Expand Down
2 changes: 1 addition & 1 deletion src/bloom-player-ui.less
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ We just make them follow the main content normally. */
}
}

#replay-button {
.bloomPlayer .replay-button {
font-size: 45px;
left: calc(50% - 22px);
top: calc(50% - 22px);
Expand Down
3 changes: 1 addition & 2 deletions src/dragActivityRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
// For now, in Bloom desktop, this file is only used in the Play tab of drag activities,
// so both live there. In Bloom player, both live in the root src directory.
// In the long run, the answer is probably a folder, or even an npm package, for all
// the stuff that the two progams share...or maybe we can make bloom player publish
// the stuff that the two programs share...or maybe we can make bloom player publish
// these files along with the output bundle and have bloom desktop use them from there.
// For now, though, it's much easier to just edit them and have them built automatically
// than to have this code in another repo.

import { get } from "jquery";
import {
kAudioSentence,
playAllAudio,
Expand Down
15 changes: 13 additions & 2 deletions src/narration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1220,7 +1220,12 @@ export function hidingPage() {

// Play the specified elements, one after the other. When the last completes (or at once if the array is empty),
// perform the 'then' action (typically used to play narration, which we put after videos).
// Todo: Bloom Player version, at least, should work with play/pause/resume/change page architecture.
//
// Note, there is a very similar function in narration.ts. It would be nice to combine them, but
// there are various reasons that is difficult at the moment. e.g.:
// 1. See comment below about sharing code with Bloom Desktop.
// 2. The other version handles play/pause which doesn't apply in BloomDesktop.
//
// (This function would be more natural in video.ts. But at least for now I'm trying to minimize the
// number of source files shared with Bloom Desktop, and we need this for Bloom Games.)
export function playAllVideo(elements: HTMLVideoElement[], then: () => void) {
Expand All @@ -1244,5 +1249,11 @@ export function playAllVideo(elements: HTMLVideoElement[], then: () => void) {
);
// Review: do we need to do something to let the rest of the world know about this?
setCurrentPlaybackMode(PlaybackMode.VideoPlaying);
video.play();

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);
});
}
Loading

0 comments on commit 871ec47

Please sign in to comment.