From 7bef092013d1952c006919812467d8a4678b60f7 Mon Sep 17 00:00:00 2001 From: infiinte-persistence <inf.persistence@gmail.com> Date: Wed, 15 Jul 2020 19:20:27 +0800 Subject: [PATCH] Add option to retry video stream on failure ## Issue Closes 4475 Option to retry video stream on failure --- CHANGELOG.md | 3 +- static/app-strings.json | 1 + .../viewers/videoViewer/internal/videojs.jsx | 74 ++++++++++++++----- 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c9c2abdc..452e04c5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - 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 diff --git a/static/app-strings.json b/static/app-strings.json index 89b8f9eb4..497a6108b 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -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.", "%view_count% Views": "%view_count% Views", "Tap to unmute": "Tap to unmute", + "Retry": "Retry", "Playing in %seconds_left% seconds...": "Playing in %seconds_left% seconds...", "Autoplay timer paused.": "Autoplay timer paused.", "0 Bytes": "0 Bytes", diff --git a/ui/component/viewers/videoViewer/internal/videojs.jsx b/ui/component/viewers/videoViewer/internal/videojs.jsx index 78954600e..c542c443c 100644 --- a/ui/component/viewers/videoViewer/internal/videojs.jsx +++ b/ui/component/viewers/videoViewer/internal/videojs.jsx @@ -1,5 +1,5 @@ // @flow -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import Button from 'component/button'; import * as ICONS from 'constants/icons'; 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 eventTracking from 'videojs-event-tracking'; import isUserTyping from 'util/detect-typing'; +const isDev = process.env.NODE_ENV !== 'production'; export type Player = { 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) { const { startMuted, source, sourceType, poster, isAudio, onPlayerReady } = props; + const [reload, setReload] = useState('initial'); let player: ?Player; const containerRef = useRef(); const videoJsOptions = { @@ -97,17 +99,33 @@ export default React.memo<Props>(function VideoJs(props: Props) { videoJsOptions.muted = startMuted; 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. - // The button only needs to appear once per session. - if (tapToUnmuteRef.current) { - const curState = tapToUnmuteRef.current.style.visibility === 'visible'; + if (theRef.current) { + const curState = theRef.current.style.visibility === 'visible'; 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); } } - showTapToUnmute(false); + showTapButton(TAP.UNMUTE, false); + } + + function retryVideoAfterFailure() { + if (player) { + setReload(Date.now()); + showTapButton(TAP.RETRY, false); + } } function onInitialPlay() { if (player && (player.muted() || player.volume() === 0)) { // The css starts as "hidden". We make it visible here without // re-rendering the whole thing. - showTapToUnmute(true); + showTapButton(TAP.UNMUTE, true); } + + showTapButton(TAP.RETRY, false); } function onVolumeChange() { if (player && !player.muted()) { - showTapToUnmute(false); + showTapButton(TAP.UNMUTE, false); } } function onError() { - showTapToUnmute(false); + showTapButton(TAP.UNMUTE, false); + showTapButton(TAP.RETRY, true); + if (player && player.loadingSpinner) { player.loadingSpinner.hide(); } } function onEnded() { - showTapToUnmute(false); + showTapButton(TAP.UNMUTE, false); + showTapButton(TAP.RETRY, false); } function handleKeyDown(e: KeyboardEvent) { @@ -225,9 +255,9 @@ export default React.memo<Props>(function VideoJs(props: Props) { }); return ( - // $FlowFixMe - <div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef}> - { + reload && ( + // $FlowFixMe + <div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef}> <Button label={__('Tap to unmute')} button="link" @@ -236,7 +266,15 @@ export default React.memo<Props>(function VideoJs(props: Props) { onClick={unmuteAndHideHint} ref={tapToUnmuteRef} /> - } - </div> + <Button + label={__('Retry')} + button="link" + icon={ICONS.REFRESH} + className="video-js--tap-to-unmute" + onClick={retryVideoAfterFailure} + ref={tapToRetryRef} + /> + </div> + ) ); });