From ab893d9e9c65304be830cdb51de87398105c18fa Mon Sep 17 00:00:00 2001 From: Mickael Kerjean Date: Sun, 16 Apr 2023 22:03:25 +1000 Subject: [PATCH] feature (video): revamp the video player + chromecast integration --- README.md | 1 + client/assets/icons/video.png | Bin 0 -> 21793 bytes client/index.js | 9 +- client/model/chromecast.js | 2 - client/pages/viewerpage/videoplayer.js | 396 +++++++++++++++++++++-- client/pages/viewerpage/videoplayer.scss | 172 ++++++++-- package.json | 4 +- 7 files changed, 515 insertions(+), 69 deletions(-) create mode 100644 client/assets/icons/video.png diff --git a/README.md b/README.md index 00426d8c..9b6aae36 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ - Manage your files from a browser - Authentication middleware to connect to various source of user - Flexible Share mechanism +- Chromecast support for images, music, and videos - Video player - Video transcoding (mov, mkv, avi, mpeg, and more) - Image viewer diff --git a/client/assets/icons/video.png b/client/assets/icons/video.png new file mode 100644 index 0000000000000000000000000000000000000000..777cee47221e51e00dbe54d5534bf1d125318c44 GIT binary patch literal 21793 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 (s); - } + const $container = useRef(); + const [isPlaying, setIsPlaying] = useState(false); + const [volume, setVolume] = useState(settings_get("volume") === null ? 50 : settings_get("volume")); + const [currentTime, setCurrentTime] = useState(0); + const [duration, setDuration] = useState(0); + const [isChromecast, setIsChromecast] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [isBuffering, setIsBuffering] = useState(false); + const [render, setRender] = useState(0); + const [hint, setHint] = useState(null); + const [videoSources, setVideoSources] = useState([]); useEffect(() => { - const player = videojs($video.current, { - controls: true, - sources: window.overrides["video-map-sources"]([{ - src: data, - type: getMimeType(data), - }]), - }); - return () => { - player.dispose(); + if (!$video.current) return; + const metadataHandler = () => { + $video.current.volume = volume / 100; + setDuration($video.current.duration); + setIsLoading(false); }; - }, [data]); + const finishHandler = () => { + $video.current.currentTime = 0; + setIsPlaying(false); + }; + const errorHandler = (err) => { + console.error(err); + notify.send(t("Not supported"), "error"); + setIsPlaying(false); + setIsLoading(false); + }; + const waitingHandler = (e) => { + setIsBuffering(true); + } + const playingHandler = (e) => { + setIsBuffering(false); + } + if (!window.overrides["video-map-sources"]) { + window.overrides["video-map-sources"] = (s) => (s); + } + const sources = window.overrides["video-map-sources"]([{ + src: data, + type: getMimeType(data), + }]); + setVideoSources(sources.map((source) => { + if (source.type !== "application/x-mpegURL" && source.type !== "application/vnd.apple.mpegurl") return source; + const h = new hls(); + h.loadSource(source.src); + h.attachMedia($video.current); + return source; + })); + + $video.current.addEventListener("loadeddata", metadataHandler); + $video.current.addEventListener("ended", finishHandler); + $video.current.addEventListener("error", errorHandler); + $video.current.addEventListener("waiting", waitingHandler); + $video.current.addEventListener("playing", playingHandler); + + let $sources = $video.current.querySelectorAll("source") + for (let i=0; i<$sources.length; i++) { + $sources[i].addEventListener("error", errorHandler); + } + return () => { + $video.current.removeEventListener("loadeddata", metadataHandler); + $video.current.removeEventListener("ended", finishHandler); + $video.current.removeEventListener("error", errorHandler); + $video.current.removeEventListener("waiting", waitingHandler); + $video.current.removeEventListener("playing", playingHandler); + for (let i=0; i<$sources.length; i++) { + $sources[i].removeEventListener("error", errorHandler); + } + }; + }, [$video, data]); + + useEffect(() => { + const resizeHandler = () => setRender(render + 1); + const onKeyPressHandler = (e) => { + if(e.code !== "Space") { + return + } + isPlaying ? onPause(e) : onPlay(e); + }; + window.addEventListener("resize", resizeHandler); + window.addEventListener("keypress", onKeyPressHandler); + return () => { + window.removeEventListener("resize", resizeHandler); + window.removeEventListener("keypress", onKeyPressHandler); + }; + }, [render, isPlaying, isChromecast]); + + useEffect(() => { + const context = Chromecast.context(); + if (!context) return; + document.getElementById("chromecast-target").append(document.createElement("google-cast-launcher")); + + const chromecastSetup = (event) => { + switch (event.sessionState) { + case cast.framework.SessionState.SESSION_STARTING: + setIsChromecast(true); + setIsLoading(true); + break; + case cast.framework.SessionState.SESSION_START_FAILED: + setIsChromecast(false); + setIsLoading(false); + break; + case cast.framework.SessionState.SESSION_STARTED: + chromecastLoader() + break; + case cast.framework.SessionState.SESSION_ENDING: + setIsChromecast(false); + $video.current.currentTime = _currentTime; + $video.current.muted = false; + if (isPlaying) $video.current.play(); + break; + } + }; + context.addEventListener( + cast.framework.CastContextEventType.SESSION_STATE_CHANGED, + chromecastSetup, + ); + return () => { + const media = Chromecast.media(); + if (media) media.removeUpdateListener(chromecastMediaHandler); + context.removeEventListener( + cast.framework.CastContextEventType.SESSION_STATE_CHANGED, + chromecastSetup, + ); + }; + }, []); + + useEffect(() => { + const interval = setInterval(() => { + if (isLoading) return; + if (isChromecast) { + const media = Chromecast.media(); + if (!media) return; + _currentTime = media.getEstimatedTime(); + setIsBuffering(media.playerState === "BUFFERING"); + } else _currentTime = $video.current.currentTime; + setCurrentTime(_currentTime) + }, 100); + return () => { + clearInterval(interval); + }; + }, [data, isChromecast, isLoading]); + + const onPlay = () => { + setIsPlaying(true); + if (isChromecast) { + const media = Chromecast.media(); + if (media) media.play(); + } else $video.current.play(); + }; + const onPause = () => { + setIsPlaying(false); + if (isChromecast) { + const media = Chromecast.media(); + if (media) media.pause(); + } else $video.current.pause(); + + }; + const onSeek = (newTime) => { + if (isChromecast) { + const media = Chromecast.media(); + if (!media) return; + setIsLoading(true); + const seekRequest = new chrome.cast.media.SeekRequest(); + seekRequest.currentTime = parseInt(newTime); + media.seek(seekRequest); + setTimeout(() => setIsLoading(false), 1000); + } else $video.current.currentTime = newTime; + }; + const onClickSeek = (e) => { + let $progress = e.target; + if (e.target.classList.contains("progress") == false) { + $progress = e.target.parentElement; + } + const rec = $progress.getBoundingClientRect(); + e.persist(); + let n = (e.clientX - rec.x) / rec.width; + if (n < 2/100) { + onPause(); + n = 0; + } + onSeek(n * duration); + }; + + const onVolumeChange = (n) => { + settings_put("volume", n); + setVolume(n); + if (isChromecast) { + const session = Chromecast.session() + if (session) session.setVolume(n / 100); + else notify.send(t("Cannot establish a connection"), "error"); + } + else $video.current.volume = n / 100; + }; + + const onProgressHover = (e) => { + const rec = e.target.getBoundingClientRect(); + const width = e.clientX - rec.x; + const time = duration * width / rec.width; + let posX = width; + posX = Math.max(posX, 30) // min boundary + posX = Math.min(posX, e.target.clientWidth - 30); + setHint({ x: `${posX}px`, time }) + }; + + const onRequestFullscreen = () => { + const session = Chromecast.session(); + if (!session) { + document.querySelector(".video_screen").requestFullscreen(); + requestAnimationFrame(() => setRender(render + 1)); + } else { + chromecastLoader(); + } + }; + + const isFullscreen = () => { + if (!$container.current) return false + return window.innerHeight === screen.height; + }; + + const renderBuffer = () => { + if (!$video.current) return null; + const calcWidth = (i) => { + return ($video.current.buffered.end(i) - $video.current.buffered.start(i)) / duration * 100; + }; + const calcLeft = (i) => { + return $video.current.buffered.start(i) / duration * 100; + }; + return ( + + { + Array.apply(null, { length: $video.current.buffered.length }).map((_, i) => ( +
+ )) + } + + ); + }; + const formatTimecode = (seconds) => { + return String(parseInt(seconds / 60)).padStart(2, "0") + + ":"+ + String(parseInt(seconds % 60)).padStart(2, "0"); + }; + + const chromecastLoader = () => { + const link = Chromecast.createLink(data); + const media = new chrome.cast.media.MediaInfo( + link, + getMimeType(data), + ); + media.metadata = new chrome.cast.media.MovieMediaMetadata() + media.metadata.title = filename.substr(0, filename.lastIndexOf(filepath.extname(filename))); + media.metadata.subtitle = CONFIG.name; + media.metadata.images = [ + new chrome.cast.Image(origin + "/assets/icons/video.png"), + ]; + + setIsChromecast(true); + setIsPlaying(true); + setIsLoading(false); + $video.current.muted = true; + $video.current.pause(); + + const session = Chromecast.session(); + if (!session) return; + $video.current.pause(); + setVolume(session.getVolume() * 100); + Chromecast.createRequest(media) + .then((req) => { + req.currentTime = parseInt($video.current.currentTime); + setCurrentTime($video.current.currentTime); + return session.loadMedia(req); + }) + .then(() => { + const media = session.getMediaSession(); + if (!media) return; + media.addUpdateListener(chromecastMediaHandler); + }) + .catch((err) => { + console.error(err); + notify.send(t("Cannot establish a connection"), "error"); + }); + }; + const chromecastMediaHandler = (isAlive) => { + if (isAlive) return; + const session = Chromecast.session(); + if (session) { + session.endSession(); + $video.current.muted = false; + setVolume($video.current.volume * 100); + setIsChromecast(false); + onSeek(0); + onPause(); + } + }; return (
-
+
-
- +
+
+ +
+ { + isLoading && ( +
+ +
+ ) + } + { + duration > 0 && ( +
+
setHint(null)}> + { isChromecast === false && renderBuffer() } +
+
+
+
+
+ { + isLoading || isBuffering ? ( + + ) : isPlaying ? ( + + ) : ( + + ) + } + onVolumeChange(0)} name={volume === 0 ? "volume_mute" : volume < 50 ? "volume_low" : "volume"}/> + onVolumeChange(Number(e.target.value))} value={volume} min="0" max="100" /> + + { formatTimecode(currentTime) } +   /   + { formatTimecode(duration) } + { + hint && ( +
{ formatTimecode(hint.time) }
+ ) + } +
+
+ { + + + + } +
+
+ ) + }
@@ -57,3 +391,5 @@ export function VideoPlayer({ filename, data, path }) {
); } + +let _currentTime = 0; // trick to avoid making too many call to the chromecast SDK diff --git a/client/pages/viewerpage/videoplayer.scss b/client/pages/viewerpage/videoplayer.scss index 53859754..3bca5ab3 100644 --- a/client/pages/viewerpage/videoplayer.scss +++ b/client/pages/viewerpage/videoplayer.scss @@ -1,54 +1,160 @@ -.component_videoplayer{ +.component_videoplayer { background: #525659; display: flex; flex: 1; flex-direction: column; width: 100%; - .video_container{ + .video_container { display: flex; flex: 1; - text-align: center; - background: #525659; overflow: hidden; padding: 15px 10px 65px 10px; height: 100%; box-sizing: border-box; - > span{ - height: 100%; + > span { display: flex; - flex-direction: column; - max-width: 800px; - margin: 0 auto; + flex: 1; + text-align: center; + overflow: hidden; + height: 100%; + box-sizing: border-box; + } + + .video_screen { + background: black; + box-shadow: rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px; + position: relative; + border-radius: 3px; + margin: auto; + position: relative; + width: 800px; + + .video_wrapper { height: 450px; } + .loader { + position: absolute; + top: 30%; + width: 100%; + } + + &.video-state-pause .videoplayer_control { opacity: 1; transition: 0.1s opacity ease; } + &.video-state-play:hover .videoplayer_control { opacity: 1; transition: 0.1s opacity ease; } + .videoplayer_control { + transition: 0.5s opacity ease; + opacity: 0; + + display: flex; + text-align: left; + position: absolute; + bottom: 0; + width: 100%; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADCCAYAAACIaaiTAAAAAXNSR0IArs4c6QAAARJJREFUOE9lyNdHBQAAhfHb3nvvuu2997jNe29TJJEkkkgSSSSJJJJEEkkiifRH5jsP56Xz8PM5gcC/xfCIWBNHiXiTQIlEk0SJZJNCiVRIM+mUyDCZlMgy2ZTIMbmUyDP5lCgwhZQoMsWUKDGllCgz5ZSogEpTRYlqU0OJoKmlRJ2pp0SDaaREk2mmRItppUSbaadEh+mkRBd0mx5K9Jo+SvSbAUoMmiFKDJsRSoyaMUqMmwlKhMwkJabMNCVmYNbMUSJsIpSImnlKLJhFSiyZZWoFVmEN1mEDNmELtmEHdmEP9uEADuEIjuEETuEMzuECLuEKruEGbuEO7uEBHuEJnuEFXuEN3uEDPuELvuEHfv8AoRErEi7Uc8UAAAAASUVORK5CYII=); + background-repeat: repeat-x; + background-size: contain; + padding: 0 0 7px 0; + + img { + cursor: pointer; + width: 25px; + filter: brightness(0) invert(1); + padding: 5px 5px 5px 10px; + } + input[type="range"] { + width: 60px; + cursor: pointer; + outline: none; + -webkit-appearance: none; + background: transparent; + + &::-webkit-slider-thumb { + -webkit-appearance: none; + height: 12px; + width: 12px; + border: none; + border-radius: 50%; + background: white; + margin-top: -5px; + } + &::-webkit-slider-runnable-track { + width: 100%; + height: 2px; + background-color: rgba(255,255,255,0.7); + border-radius: 2px; + } + &::-moz-range-track { + width: 100%; + height: 2px; + background-color: rgba(255,255,255,0.7); + border-radius: 2px; + } + } + .timecode { + color: white; + margin: auto 0; + padding-left: 10px; + .hint { + position: absolute; + top: -40px; + margin-left: -23px; + font-size: 0.9rem; + background: #f1f1f155; + border-radius: 3px; + padding: 2px 5px; + background: var(--dark); + } + } + .progress { + position: absolute; + width: 100%; + top: -20px; + height: 20px; + cursor: pointer; + + .progress-active, .progress-buffer, .progress-placeholder { + top: 10px; + position: absolute; + height: 4px; + } + .progress-active { + background: var(--primary); + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + .thumb { + display: none; + float: right; + width: 4px; + height: 4px; + background: rgba(255,255,255,0.1); + border-radius: 50%; + margin-top: 0px; + position: relative; + left: 3px; + } + } + .progress-buffer { + background: #e2e2e244; + } + .progress-placeholder { + background: #e2e2e222; + width: 100%; + } + } + .pull-right { + margin-left: auto; + padding-right: 10px; + } + } + } + video { width: 100%; - > div{margin: auto;} + height: 100%; } } } - - -/* SKIN FOR VIDEOJS: */ -.video-js{ - font-size: 14px; /* Chromium bug */ - max-height: 500px; - - .vjs-control-bar{ - background: black; - background: linear-gradient(transparent 20%, #00000099); - } - .vjs-load-progress div{ - background: var(--primary); - } - .vjs-big-play-button{ - background-color: var(--primary)!important; - border-color: var(--emphasis-primary)!important; - border-width: 2px; - margin-top: -40px; - border-radius: 8px; - &:before{ font-size: 45px; color: var(--bg-color); } - } -} +.component_videoplayer .video_container .video_screen .videoplayer_control:hover .progress .progress-active .thumb { display: block; } .video-enter, .video-appear{ diff --git a/package.json b/package.json index f36bc50a..7b8f6891 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "codemirror": "^5.26.0", "epubjs": "^0.3.93", "exif-js": "^2.3.0", + "hls.js": "^1.4.0", "little-loader": "^0.2.0", "prop-types": "^15.5.10", "react": "^16.5.0", @@ -33,9 +34,6 @@ "react-selectable": "git+https://github.com/mickael-kerjean/react-selectable.git", "react-sticky": "^6.0.2", "rxjs": "^5.4.0", - "video.js": "^7.14.3", - "videojs-contrib-hls": "^5.14.1", - "videojs-sublime-skin": "^1.0.3", "wavesurfer.js": "^1.4.0" }, "devDependencies": {