// video events
React.useEffect(() => {
const vid = videoRef.current;
const duration = durationRef.current;
if (duration)
vid.onloadeddata = () => {
duration.textContent = getTimeStr(vid.duration);
};
vid.onplay = () => {
setPlaying(true);
};
vid.onpause = () => {
setPlaying(false);
};
vid.onwaiting = () => {
setLoading(true);
};
vid.oncanplay = () => {
if (!vid.seeking) {
setLoading(false);
setEnded(false);
}
};
return () => {
vid.onplay = null;
vid.onpause = null;
vid.onwaiting = null;
vid.oncanplay = null;
};
}, [durationRef, readyToLoad, setEnded, setLoading, setPlaying, videoRef]);var playPromise = document.querySelector('video').play();
// In browsers that don’t yet support this functionality,
// playPromise won’t be defined.
if (playPromise !== undefined) {
playPromise.then(function() {
// Automatic playback started!
}).catch(function(error) {
// Automatic playback failed.
// Show a UI element to let the user manually start playback.
});
}React.useEffect(() => {
const vid = videoRef.current;
if (!readyToLoad || !vid) return;
vid.ontimeupdate = () => {
if (!vid.seeking) handleTimeChange(vid.currentTime, vid.duration);
};
vid.onseeking = () => {
handleTimeChange(vid.currentTime, vid.duration);
};
return () => {
vid.ontimeupdate = null;
vid.onseeking = null;
};
}, [
handleTimeChange,
readyToLoad,
videoRef,
]);
const backgroundImage = `linear-gradient(90deg, rgb(51, 151, 207) 0%, rgb(51, 151, 207) 30%, transparent 30%, transparent 100%);`let linearGradientBody = '';
for (let i = 0; i < vid.buffered.length; i++) {
// firefox will have error
const startPercent = trimTimePercent(
(vid.buffered.start(i) / duration) * 100
);
const endPercent = trimTimePercent(
(vid.buffered.end(i) / duration) * 100
);
if (endPercent > currentPercent)
linearGradientBody += `${basicColor} ${startPercent}%, ${bufferedColor} ${startPercent}%, ${bufferedColor} ${endPercent}%, ${basicColor} ${endPercent}%, `;
}React.useEffect(() => {
let timer: number | undefined;
const handleMouseDown = () => {
prevPlayingStatusRef.current = playing;
// NOTE: PART1 prevent video didn't receive onplay event if click too fast
timer = window.setTimeout(() => vid.pause(), 50);
};
const handleMouseUp = () => {
if (prevPlayingStatusRef.current && vid.currentTime < vid.duration) {
// NOTE: PART2 prevent video didn't receive on play event if click too fast
if (typeof timer === 'number') window.clearTimeout(timer);
vid.play();
}
};
}, [readyToLoad, playing, videoRef, progressBarRef]);(function () {
'use strict';
const document =
typeof window !== 'undefined' && typeof window.document !== 'undefined'
? window.document
: {};
const isCommonjs = typeof module !== 'undefined' && module.exports;
const fn = (function () {
let val;
const fnMap = [
[
'requestFullscreen',
'exitFullscreen',
'fullscreenElement',
'fullscreenEnabled',
'fullscreenchange',
'fullscreenerror',
],
// New WebKit
[
'webkitRequestFullscreen',
'webkitExitFullscreen',
'webkitFullscreenElement',
'webkitFullscreenEnabled',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
// Old WebKit
[
'webkitRequestFullScreen',
'webkitCancelFullScreen',
'webkitCurrentFullScreenElement',
'webkitCancelFullScreen',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
[
'mozRequestFullScreen',
'mozCancelFullScreen',
'mozFullScreenElement',
'mozFullScreenEnabled',
'mozfullscreenchange',
'mozfullscreenerror',
],
[
'msRequestFullscreen',
'msExitFullscreen',
'msFullscreenElement',
'msFullscreenEnabled',
'MSFullscreenChange',
'MSFullscreenError',
],
];
let i = 0;
const l = fnMap.length;
const ret = {};
for (; i < l; i++) {
val = fnMap[i];
if (val && val[1] in document) {
for (i = 0; i < val.length; i++) {
ret[fnMap[0][i]] = val[i];
}
return ret;
}
}
return false;
})();
const eventNameMap = {
change: fn.fullscreenchange,
error: fn.fullscreenerror,
};
const screenfull = {
request: function (element, options) {
return new Promise(
function (resolve, reject) {
var onFullScreenEntered = function () {
this.off('change', onFullScreenEntered);
resolve();
}.bind(this);
this.on('change', onFullScreenEntered);
element = element || document.documentElement;
const returnPromise = element[fn.requestFullscreen](options);
if (returnPromise instanceof Promise) {
returnPromise.then(onFullScreenEntered).catch(reject);
}
}.bind(this)
);
},
exit: function () {
return new Promise(
function (resolve, reject) {
if (!this.isFullscreen) {
resolve();
return;
}
var onFullScreenExit = function () {
this.off('change', onFullScreenExit);
resolve();
}.bind(this);
this.on('change', onFullScreenExit);
const returnPromise = document[fn.exitFullscreen]();
if (returnPromise instanceof Promise) {
returnPromise.then(onFullScreenExit).catch(reject);
}
}.bind(this)
);
},
toggle: function (element, options) {
return this.isFullscreen ? this.exit() : this.request(element, options);
},
onchange: function (callback) {
this.on('change', callback);
},
onerror: function (callback) {
this.on('error', callback);
},
on: function (event, callback) {
const eventName = eventNameMap[event];
if (eventName) {
document.addEventListener(eventName, callback, false);
}
},
off: function (event, callback) {
const eventName = eventNameMap[event];
if (eventName) {
document.removeEventListener(eventName, callback, false);
}
},
raw: fn,
};
if (!fn) {
if (isCommonjs) {
module.exports = { isEnabled: false };
} else {
window.screenfull = { isEnabled: false };
}
return;
}
Object.defineProperties(screenfull, {
isFullscreen: {
get: function () {
return Boolean(document[fn.fullscreenElement]);
},
},
element: {
enumerable: true,
get: function () {
return document[fn.fullscreenElement];
},
},
isEnabled: {
enumerable: true,
get: function () {
// Coerce to boolean in case of old WebKit
return Boolean(document[fn.fullscreenEnabled]);
},
},
});
if (isCommonjs) {
module.exports = screenfull;
} else {
window.screenfull = screenfull;
}
})();
const toggleFullscreen = useCallback(() => {
const vid = videoRef.current;
if (screenfull.isEnabled) {
if (screenfull.isFullscreen) {
screenfull.exit();
} else {
const target = targetRef.current;
if (isSafariBrowser) {
screenfull.request(target ? target : undefined);
} else {
screenfull.request();
}
}
} else {
if (vid && vid.webkitEnterFullscreen) {
onEnteriOSFullscreen();
vid.webkitEnterFullscreen();
}
}
}, [targetRef, onEnteriOSFullscreen, videoRef]);onScreen
onScreen
onScreen
onScreen
onScreen
export const getFirstEngagement = <T extends HTMLElement>(
container: T | null,
dataAttribute: string
): string | null => {
if (typeof document === 'undefined') return null;
const selector = `${scoped}[${dataAttribute}]:not([${dataAttribute}='false'])`;
const element = (container || document).querySelector(selector);
if (element) return element.getAttribute(dataAttribute);
return null;
};onScreen
onScreen
onScreen
onScreen
true
false
true
true
Current Playing Video
Global State
useEffect(() => {
// trigger if currentPlayingVideo changed and not ended
if (!ended) {
if (currentPlayingVideo === uniqAutoPlayId) {
setTimeout(() => {
onAutoPlay?.();
handlePlay();
}, 50);
} else {
handlePause();
}
}
});A
B
Play A
A
B
Play A
Pause A
A
B
Play A
Pause A
Play B
A
B
Play A
Pause A
Play B
Pause B
A
B
Play B
A
B
Play B
Play A
A
B
Play B
Play A
Pause B
A
B
Play B
Play A
Pause B
Pause A
useEffect(() => {
// trigger if currentPlayingVideo changed and not ended
if (!ended) {
if (currentPlayingVideo === uniqAutoPlayId) {
setTimeout(() => {
onAutoPlay?.();
handlePlay();
}, 50);
} else {
handlePause();
}
}
});Atomic
VideoController
Play A
Queue
Pause A
Play B
Pause B
Play C
Pause C
Play A
Queue
Pause A
Play B
Pause B
Play C
Pause C
Atomic
VideoController
Play A
Queue
Pause A
Play B
Pause B
Play C
Pause C
Atomic
VideoController
Play A
Queue
Play B
Pause B
Play C
Pause C
Pause A
Play C
Atomic
VideoController
Play A
Queue
Play B
Pause B
Play C
Pause C
Pause A
Play C
Atomic
VideoController
Play A
Queue
Play B
Pause B
Play C
Pause C
Pause A
Play C
Atomic
VideoController
// handle atomic play video
const { handlePlay, handlePause } = useAtomicVideoController({
handlePlay: _handlePlay,
handlePause: _handlePause,
});
export default function useAtomicVideoController({
handlePlay,
handlePause,
}: {
handlePlay: () => void;
handlePause: () => void;
}) {
// handle atomic play video
const _handlePlay = useCallback(() => {
atomicVideoController.play({
play: handlePlay,
pause: handlePause,
});
}, [handlePlay, handlePause]);
return { handlePlay: _handlePlay, handlePause } as const;
}