diff --git a/src/components/DriveVideo/index.jsx b/src/components/DriveVideo/index.jsx
index b35f95b7..f28606aa 100644
--- a/src/components/DriveVideo/index.jsx
+++ b/src/components/DriveVideo/index.jsx
@@ -12,7 +12,7 @@ import Colors from '../../colors';
import { ErrorOutline } from '../../icons';
import { currentOffset } from '../../timeline';
import { seek, bufferVideo } from '../../timeline/playback';
-import { isIos, isFirefox } from '../../utils/browser.js';
+import { isIos } from '../../utils/browser.js';
const VideoOverlay = ({ loading, error }) => {
let content;
@@ -37,30 +37,12 @@ const VideoOverlay = ({ loading, error }) => {
);
};
-const getVideoState = (videoPlayer) => {
- const currentTime = videoPlayer.getCurrentTime();
- const { buffered } = videoPlayer.getInternalPlayer();
-
- let bufferRemaining = -1;
- for (let i = 0; i < buffered.length; i++) {
- const end = buffered.end(i);
- if (currentTime >= buffered.start(i) && currentTime <= end) {
- bufferRemaining = end - currentTime;
- break;
- }
- }
-
- return {
- bufferRemaining,
- hasLoaded: bufferRemaining > 0,
- };
-};
-
class DriveVideo extends Component {
constructor(props) {
super(props);
this.onVideoBuffering = this.onVideoBuffering.bind(this);
+ this.onVideoBufferEnd = this.onVideoBufferEnd.bind(this);
this.onHlsError = this.onHlsError.bind(this);
this.onVideoError = this.onVideoError.bind(this);
this.onVideoResume = this.onVideoResume.bind(this);
@@ -98,22 +80,20 @@ class DriveVideo extends Component {
}
onVideoBuffering() {
- const { dispatch, currentRoute } = this.props;
+ const { dispatch } = this.props;
const videoPlayer = this.videoPlayer.current;
- if (!videoPlayer || !currentRoute || !videoPlayer.getDuration()) {
- dispatch(bufferVideo(true));
- }
-
- if (this.firstSeek) {
+ if (this.firstSeek && videoPlayer) {
this.firstSeek = false;
videoPlayer.seekTo(this.currentVideoTime(), 'seconds');
}
+ dispatch(bufferVideo(true));
+ }
- const { hasLoaded } = getVideoState(videoPlayer);
- const { readyState } = videoPlayer.getInternalPlayer();
- if (!hasLoaded || readyState < 2) {
- dispatch(bufferVideo(true));
- }
+ onVideoBufferEnd() {
+ const { dispatch, isBufferingVideo } = this.props;
+ const { videoError } = this.state;
+ if (videoError) this.setState({ videoError: null });
+ if (isBufferingVideo) dispatch(bufferVideo(false));
}
/**
@@ -201,57 +181,30 @@ class DriveVideo extends Component {
}
syncVideo() {
- const { dispatch, isBufferingVideo, isMuted } = this.props;
+ const { dispatch, isBufferingVideo, desiredPlaySpeed, currentRoute } = this.props;
const videoPlayer = this.videoPlayer.current;
if (!videoPlayer || !videoPlayer.getInternalPlayer() || !videoPlayer.getDuration()) {
return;
}
- let { desiredPlaySpeed: newPlaybackRate } = this.props;
const desiredVideoTime = this.currentVideoTime();
const curVideoTime = videoPlayer.getCurrentTime();
const timeDiff = desiredVideoTime - curVideoTime;
-
- if (Math.abs(timeDiff) <= Math.max(0.1, 0.5 * newPlaybackRate)) { // newPlaybackRate = 0 when paused, set minimum 0.1 to prevent seeking when paused
- if (!isIos()) {
- newPlaybackRate = Math.max(0, newPlaybackRate + Math.round(timeDiff * 10) / 10);
+ const videoStartOffset = currentRoute?.videoStartOffset || 0;
+
+ if (Math.abs(timeDiff) > 0.5) {
+ if (desiredVideoTime === 0 && timeDiff < 0 && curVideoTime !== videoPlayer.getDuration()) {
+ // logs start earlier than the video, snap timeline forward to where video begins
+ dispatch(seek(currentOffset() - (timeDiff * 1000)));
+ } else {
+ // user seek, loop wrap, or initial sync: push to video
+ videoPlayer.seekTo(desiredVideoTime, 'seconds');
}
- } else if (desiredVideoTime === 0 && timeDiff < 0 && curVideoTime !== videoPlayer.getDuration()) {
- // logs start earlier than video, so skip to video ts 0
- dispatch(seek(currentOffset() - (timeDiff * 1000)));
- } else {
- videoPlayer.seekTo(desiredVideoTime, 'seconds');
- }
- // most browsers don't support more than 16x playback rate, firefox mutes audio above 8x causing audio to cut in and out with timeDiff rate shifts
- newPlaybackRate = Math.max(0, Math.min((isFirefox() && !isMuted) ? 8 : 16, newPlaybackRate));
-
- const internalPlayer = videoPlayer.getInternalPlayer();
-
- const { hasLoaded } = getVideoState(videoPlayer);
- if (isBufferingVideo && internalPlayer.readyState >= 4) {
- dispatch(bufferVideo(false));
- } else if (isBufferingVideo || !hasLoaded || internalPlayer.readyState < 2) {
- if (!isBufferingVideo) {
- dispatch(bufferVideo(true));
- }
- newPlaybackRate = 0; // in some circumstances, iOS won't update readyState unless temporarily paused
- }
-
- if (videoPlayer.getInternalPlayer('hls')) {
- if (!internalPlayer.paused && newPlaybackRate === 0) {
- internalPlayer.pause();
- } else if (internalPlayer.playbackRate !== newPlaybackRate && newPlaybackRate !== 0) {
- internalPlayer.playbackRate = newPlaybackRate;
- }
- if (internalPlayer.paused && newPlaybackRate !== 0) {
- const playRes = internalPlayer.play();
- if (playRes) {
- playRes.catch(() => console.debug('[DriveVideo] play interrupted by pause'));
- }
- }
- } else {
- // TODO: fix iOS bug where video doesn't stop buffering while paused
- internalPlayer.playbackRate = newPlaybackRate;
+ } else if (Math.abs(timeDiff) > 0.05 && desiredPlaySpeed > 0 && !isBufferingVideo) {
+ // let the video play freely and pull the timeline to match, instead of fudging the playback rate.
+ // this is the core of the fix — fudging the rate every tick is what crackled audio on iOS,
+ // CarPlay, and macOS audio routes.
+ dispatch(seek(curVideoTime * 1000 + videoStartOffset));
}
}
@@ -314,7 +267,7 @@ class DriveVideo extends Component {
}}
playbackRate={desiredPlaySpeed}
onBuffer={this.onVideoBuffering}
- onBufferEnd={this.onVideoResume}
+ onBufferEnd={this.onVideoBufferEnd}
onPlay={this.onVideoResume}
onError={this.onVideoError}
/>
diff --git a/src/components/TimeDisplay/index.jsx b/src/components/TimeDisplay/index.jsx
index 1a340b1a..87c9a994 100644
--- a/src/components/TimeDisplay/index.jsx
+++ b/src/components/TimeDisplay/index.jsx
@@ -15,7 +15,6 @@ import { DownArrow, Forward10, Pause, PlayArrow, Replay10, UpArrow } from '../..
import { currentOffset } from '../../timeline';
import { seek, play, pause } from '../../timeline/playback';
import { getSegmentNumber } from '../../utils';
-import { isIos } from '../../utils/browser.js';
const timerSteps = [
0.1,
@@ -259,30 +258,28 @@ class TimeDisplay extends Component {