Show "unmute" option on videos automatically muted by browser.

Implementation:
- The code is placed in <VideoJs> instead of <VideoViewer> as we need to control the video itself. It's more self-contained here, rather than trying to pass refs around between parent and child.
- useState cannot be used as it will cause a re-render when the hint it clicked and dismissed. The DOM is used to hide the button.
This commit is contained in:
infiinte-persistence 2020-06-10 15:01:54 +02:00 committed by Sean Yesmunt
parent bbda69dc5f
commit a20ea08ac7
5 changed files with 90 additions and 2 deletions

View file

@ -1228,6 +1228,7 @@
"Sharing information with LBRY, Inc. allows us to report to publishers how their content is doing, as\n well as track basic usage and performance. This is the minimum required to earn rewards from LBRY, Inc.": "Sharing information with LBRY, Inc. allows us to report to publishers how their content is doing, as\n well as track basic usage and performance. This is the minimum required to earn rewards from LBRY, Inc.",
"No information will be sent directly to LBRY, Inc. or third-parties about your usage. Note that as\n peer-to-peer software, your IP address and potentially other system information can be sent to other\n users, though this information is not stored permanently.": "No information will be sent directly to LBRY, Inc. or third-parties about your usage. Note that as\n peer-to-peer software, your IP address and potentially other system information can be sent to other\n users, though this information is not stored permanently.",
"%view_count% Views": "%view_count% Views",
"Tap to unmute": "Tap to unmute",
"0 Bytes": "0 Bytes",
"Bytes": "Bytes",
"KB": "KB",

View file

@ -370,6 +370,13 @@ export const icons = {
<path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z" />
</g>
),
[ICONS.VOLUME_MUTED]: buildIcon(
<g>
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
<line x1="23" y1="9" x2="17" y2="15" />
<line x1="17" y1="9" x2="23" y2="15" />
</g>
),
[ICONS.IMAGE]: buildIcon(
<g>
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />

View file

@ -1,13 +1,17 @@
// @flow
import React, { useEffect, useRef } from 'react';
import Button from 'component/button';
import * as ICONS from 'constants/icons';
import classnames from 'classnames';
import videojs from 'video.js/dist/alt/video.core.novtt.min.js';
import 'video.js/dist/alt/video-js-cdn.min.css';
import eventTracking from 'videojs-event-tracking';
import isUserTyping from 'util/detect-typing';
import isDev from 'electron-is-dev';
export type Player = {
on: (string, (any) => void) => void,
one: (string, (any) => void) => void,
isFullscreen: () => boolean,
exitFullscreen: () => boolean,
requestFullscreen: () => boolean,
@ -93,6 +97,50 @@ export default React.memo<Props>(function VideoJs(props: Props) {
videoJsOptions.muted = startMuted;
const tapToUnmuteRef = useRef();
function showTapToUnmute(newState: boolean) {
// Use the DOM to control the state of the button to prevent re-renders.
// The button only needs to appear once per session.
if (tapToUnmuteRef.current) {
const curState = tapToUnmuteRef.current.style.visibility === 'visible';
if (newState !== curState) {
tapToUnmuteRef.current.style.visibility = newState ? 'visible' : 'hidden';
}
} else if (isDev) {
throw new Error('[videojs.jsx] Empty video ref should not happen');
}
}
function unmuteAndHideHint() {
if (player) {
player.muted(false);
}
showTapToUnmute(false);
}
function onInitialPlay() {
if (player && player.muted()) {
// The css starts as "hidden". We make it visible here without
// re-rendering the whole thing.
showTapToUnmute(true);
}
}
function onVolumeChange() {
if (player && !player.muted()) {
showTapToUnmute(false);
}
}
function onError() {
showTapToUnmute(false);
}
function onEnded() {
showTapToUnmute(false);
}
function handleKeyDown(e: KeyboardEvent) {
const videoNode: ?HTMLVideoElement = containerRef.current && containerRef.current.querySelector('video, audio');
@ -145,6 +193,11 @@ export default React.memo<Props>(function VideoJs(props: Props) {
player = videojs(el, videoJsOptions, () => {
if (player) {
player.one('play', onInitialPlay);
player.on('volumechange', onVolumeChange);
player.on('error', onError);
player.on('ended', onEnded);
onPlayerReady(player);
}
});
@ -166,6 +219,19 @@ export default React.memo<Props>(function VideoJs(props: Props) {
}
});
// $FlowFixMe
return <div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef} />;
return (
// $FlowFixMe
<div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef}>
{
<Button
label={__('Tap to unmute')}
button="link"
icon={ICONS.VOLUME_MUTED}
className="video-js--tap-to-unmute"
onClick={unmuteAndHideHint}
ref={tapToUnmuteRef}
/>
}
</div>
);
});

View file

@ -90,6 +90,7 @@ export const MORE_VERTICAL = 'MoreVertical';
export const IMAGE = 'Image';
export const AUDIO = 'HeadPhones';
export const VIDEO = 'Video';
export const VOLUME_MUTED = 'VolumeX';
export const TEXT = 'FileText';
export const DOWNLOADABLE = 'Downloadable';
export const REPOST = 'Repeat';

View file

@ -345,6 +345,19 @@
}
}
.video-js--tap-to-unmute {
visibility: hidden; // Start off as hidden.
z-index: 2;
position: absolute;
top: 5%;
left: 5%;
padding: var(--spacing-xs) var(--spacing-m); // Make it comfy for touch.
color: var(--color-gray-1);
background: black;
border: 1px solid var(--color-gray-4);
opacity: 0.7;
}
.file-render {
.video-js {
display: flex;