Add option to retry video stream on failure
## Issue Closes 4475 Option to retry video stream on failure
This commit is contained in:
parent
115c456318
commit
7bef092013
3 changed files with 59 additions and 19 deletions
|
@ -9,7 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Allow zooming on Desktop _community pr!_ ([#4513](https://github.com/lbryio/lbry-desktop/pull/4513))
|
- Allow zooming on Desktop _community pr!_ ([#4513](https://github.com/lbryio/lbry-desktop/pull/4513))
|
||||||
- Show "YT Creator" label in File Page as well _community pr!_ ([#4523](https://github.com/lbryio/lbry-desktop/pull/4523))
|
- Show "YT Creator" label in File Page as well _community pr!_ ([#4523](https://github.com/lbryio/lbry-desktop/pull/4523))
|
||||||
|
- Add option to retry video stream on failure _community pr!_ ([#4541](https://github.com/lbryio/lbry-desktop/pull/4541))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -1162,6 +1162,7 @@
|
||||||
"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.",
|
"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",
|
"%view_count% Views": "%view_count% Views",
|
||||||
"Tap to unmute": "Tap to unmute",
|
"Tap to unmute": "Tap to unmute",
|
||||||
|
"Retry": "Retry",
|
||||||
"Playing in %seconds_left% seconds...": "Playing in %seconds_left% seconds...",
|
"Playing in %seconds_left% seconds...": "Playing in %seconds_left% seconds...",
|
||||||
"Autoplay timer paused.": "Autoplay timer paused.",
|
"Autoplay timer paused.": "Autoplay timer paused.",
|
||||||
"0 Bytes": "0 Bytes",
|
"0 Bytes": "0 Bytes",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
@ -7,6 +7,7 @@ import videojs from 'video.js/dist/alt/video.core.novtt.min.js';
|
||||||
import 'video.js/dist/alt/video-js-cdn.min.css';
|
import 'video.js/dist/alt/video-js-cdn.min.css';
|
||||||
import eventTracking from 'videojs-event-tracking';
|
import eventTracking from 'videojs-event-tracking';
|
||||||
import isUserTyping from 'util/detect-typing';
|
import isUserTyping from 'util/detect-typing';
|
||||||
|
const isDev = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
export type Player = {
|
export type Player = {
|
||||||
on: (string, (any) => void) => void,
|
on: (string, (any) => void) => void,
|
||||||
|
@ -79,6 +80,7 @@ properties for this component should be kept to ONLY those that if changed shoul
|
||||||
*/
|
*/
|
||||||
export default React.memo<Props>(function VideoJs(props: Props) {
|
export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
const { startMuted, source, sourceType, poster, isAudio, onPlayerReady } = props;
|
const { startMuted, source, sourceType, poster, isAudio, onPlayerReady } = props;
|
||||||
|
const [reload, setReload] = useState('initial');
|
||||||
let player: ?Player;
|
let player: ?Player;
|
||||||
const containerRef = useRef();
|
const containerRef = useRef();
|
||||||
const videoJsOptions = {
|
const videoJsOptions = {
|
||||||
|
@ -97,17 +99,33 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
videoJsOptions.muted = startMuted;
|
videoJsOptions.muted = startMuted;
|
||||||
|
|
||||||
const tapToUnmuteRef = useRef();
|
const tapToUnmuteRef = useRef();
|
||||||
|
const tapToRetryRef = useRef();
|
||||||
|
|
||||||
|
const TAP = {
|
||||||
|
UNMUTE: 'UNMUTE',
|
||||||
|
RETRY: 'RETRY',
|
||||||
|
};
|
||||||
|
|
||||||
|
function showTapButton(tapButton, newState) {
|
||||||
|
let theRef;
|
||||||
|
switch (tapButton) {
|
||||||
|
case TAP.UNMUTE:
|
||||||
|
theRef = tapToUnmuteRef;
|
||||||
|
break;
|
||||||
|
case TAP.RETRY:
|
||||||
|
theRef = tapToRetryRef;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (isDev) throw new Error('showTapButton: unexpected ref');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
function showTapToUnmute(newState: boolean) {
|
|
||||||
// Use the DOM to control the state of the button to prevent re-renders.
|
// Use the DOM to control the state of the button to prevent re-renders.
|
||||||
// The button only needs to appear once per session.
|
if (theRef.current) {
|
||||||
if (tapToUnmuteRef.current) {
|
const curState = theRef.current.style.visibility === 'visible';
|
||||||
const curState = tapToUnmuteRef.current.style.visibility === 'visible';
|
|
||||||
if (newState !== curState) {
|
if (newState !== curState) {
|
||||||
tapToUnmuteRef.current.style.visibility = newState ? 'visible' : 'hidden';
|
theRef.current.style.visibility = newState ? 'visible' : 'hidden';
|
||||||
}
|
}
|
||||||
} else if (process.env.NODE_ENV === 'development') {
|
|
||||||
throw new Error('[videojs.jsx] Empty video ref should not happen');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,32 +136,44 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
player.volume(1.0);
|
player.volume(1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
showTapToUnmute(false);
|
showTapButton(TAP.UNMUTE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function retryVideoAfterFailure() {
|
||||||
|
if (player) {
|
||||||
|
setReload(Date.now());
|
||||||
|
showTapButton(TAP.RETRY, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onInitialPlay() {
|
function onInitialPlay() {
|
||||||
if (player && (player.muted() || player.volume() === 0)) {
|
if (player && (player.muted() || player.volume() === 0)) {
|
||||||
// The css starts as "hidden". We make it visible here without
|
// The css starts as "hidden". We make it visible here without
|
||||||
// re-rendering the whole thing.
|
// re-rendering the whole thing.
|
||||||
showTapToUnmute(true);
|
showTapButton(TAP.UNMUTE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showTapButton(TAP.RETRY, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onVolumeChange() {
|
function onVolumeChange() {
|
||||||
if (player && !player.muted()) {
|
if (player && !player.muted()) {
|
||||||
showTapToUnmute(false);
|
showTapButton(TAP.UNMUTE, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onError() {
|
function onError() {
|
||||||
showTapToUnmute(false);
|
showTapButton(TAP.UNMUTE, false);
|
||||||
|
showTapButton(TAP.RETRY, true);
|
||||||
|
|
||||||
if (player && player.loadingSpinner) {
|
if (player && player.loadingSpinner) {
|
||||||
player.loadingSpinner.hide();
|
player.loadingSpinner.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEnded() {
|
function onEnded() {
|
||||||
showTapToUnmute(false);
|
showTapButton(TAP.UNMUTE, false);
|
||||||
|
showTapButton(TAP.RETRY, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(e: KeyboardEvent) {
|
function handleKeyDown(e: KeyboardEvent) {
|
||||||
|
@ -225,9 +255,9 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// $FlowFixMe
|
reload && (
|
||||||
<div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef}>
|
// $FlowFixMe
|
||||||
{
|
<div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef}>
|
||||||
<Button
|
<Button
|
||||||
label={__('Tap to unmute')}
|
label={__('Tap to unmute')}
|
||||||
button="link"
|
button="link"
|
||||||
|
@ -236,7 +266,15 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
onClick={unmuteAndHideHint}
|
onClick={unmuteAndHideHint}
|
||||||
ref={tapToUnmuteRef}
|
ref={tapToUnmuteRef}
|
||||||
/>
|
/>
|
||||||
}
|
<Button
|
||||||
</div>
|
label={__('Retry')}
|
||||||
|
button="link"
|
||||||
|
icon={ICONS.REFRESH}
|
||||||
|
className="video-js--tap-to-unmute"
|
||||||
|
onClick={retryVideoAfterFailure}
|
||||||
|
ref={tapToRetryRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue