Skip to content

Commit

Permalink
Merge pull request #329 from BloomBooks/improveVideoControls
Browse files Browse the repository at this point in the history
Make our own video controls (BL-13957)
  • Loading branch information
andrew-polk authored Oct 14, 2024
2 parents fab7fee + b2adbfe commit 8034977
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 106 deletions.
80 changes: 0 additions & 80 deletions src/bloom-player-core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1051,80 +1051,13 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
}
}

private showReplayButton(pageVideoData: IPageVideoComplete | undefined) {
const pageNumber =
pageVideoData?.page.getAttribute("data-page-number") ?? 0;
pageVideoData?.videos.forEach((video, index) => {
const parent = video.parentElement!;
let replayButton = document.getElementById(
`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",
`replay-button-${pageNumber}-${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
);
}
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
// when we are about to unmount.
private handlePageVideoComplete = pageVideoData => {
// Verify we're on the current page before playing audio (BL-10039)
// If the user if flipping pages rapidly, video completed events can overlap.
if (pageVideoData!.page === BloomPlayerCore.currentPage) {
this.playAudioAndAnimation(pageVideoData!.page); // play audio after video finishes
if (!this.props.hideSwiperButtons) {
// Replay isn't technically a swiper button, but it's the same sort of navigational
// control and wants to be hidden in the same circumstances, currently both preview
// and recording-in-progress in publish-to-video.
this.showReplayButton(pageVideoData);
}
// } else {
// console.log(`DEBUG: ignoring out of sequence page audio`);
}
};

Expand Down Expand Up @@ -2819,19 +2752,6 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
if (this.props.paused) {
return; // shouldn't call when paused
}
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
setCurrentNarrationPage(bloomPage);
// State must be set before calling HandlePageVisible() and related methods.
Expand Down
69 changes: 51 additions & 18 deletions src/bloom-player-ui.less
Original file line number Diff line number Diff line change
Expand Up @@ -247,24 +247,6 @@ We just make them follow the main content normally. */
}
}

.bloomPlayer .replay-button {
font-size: 45px;
left: calc(50% - 22px);
top: calc(50% - 22px);
}

.fade-out {
animation: fadeOut ease 1s;
}
@keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0.2;
}
}

// I positioned this absolutely so that, although we allow space for it when computing the
// overall zoom factor for the main page slider, it doesn't otherwise affect the positioning
// of everything, which is quite complex. Also, this will work even if we later decide that
Expand Down Expand Up @@ -446,3 +428,54 @@ body,
video::-webkit-media-controls-overlay-play-button {
display: none;
}

@videoIconSize: 30px;
@videoIconPadding: 0px;

div.videoControlContainer {
position: absolute;
top: calc(50% - @videoIconSize / 2);
height: @videoIconSize + 2 * @videoIconPadding;
width: @videoIconSize + 2 * @videoIconPadding;
padding: @videoIconPadding;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
box-sizing: border-box;
justify-content: center;
align-items: center;
display: none;
}

svg.videoControl {
height: @videoIconSize;
}

// never shown at the same time, so same location is fine.
.videoPlayIcon,
.videoPauseIcon {
left: calc(50% - @videoIconSize - 5px); // always the left of two icons
}

.videoReplayIcon {
left: calc(50% + 5px); // always the right of two icons
}

// Show play and replay when paused
.bloom-videoContainer.paused {
.videoControlContainer {
&.videoPlayIcon,
&.videoReplayIcon {
display: flex;
}
}
}

// Show pause and replay when playing and hovered.
.bloom-videoContainer.playing:hover {
.videoControlContainer {
&.videoPauseIcon,
&.videoReplayIcon {
display: flex;
}
}
}
9 changes: 9 additions & 0 deletions src/pauseIcon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const getPauseIcon = (color: string) => {
const elt = document.createElement("div");
// From MUI Pause
elt.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" fill="${color}"/>
</svg>
`;
return elt.firstChild as HTMLElement;
};
8 changes: 8 additions & 0 deletions src/playIcon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const getPlayIcon = (color: string) => {
const elt = document.createElement("div");
// from MUI PlayArrow
elt.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 5v14l11-7z" fill="${color}"/>
</svg>`;
return elt.firstChild as HTMLElement;
};
9 changes: 9 additions & 0 deletions src/replayIcon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const getReplayIcon = (color: string) => {
const elt = document.createElement("div");
// from MUI Replay
elt.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z" fill="${color}"/>
</svg>
`;
return elt.firstChild as HTMLElement;
};
Loading

0 comments on commit 8034977

Please sign in to comment.