@mux/mux-player-react
Version:
An open source Mux player for React that Just Worksâ„¢
8 lines (7 loc) • 15.2 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/news/index.tsx", "../../src/news/playlist-end-screen.tsx", "../../src/news/playlist-end-screen.css"],
"sourcesContent": ["import React, { useEffect, useRef, useState } from 'react';\nimport MuxPlayer, { MuxPlayerProps } from '@mux/mux-player-react/ads';\nimport NewsTheme from '@mux/mux-player-react/themes/news';\nimport PlaylistEndScreen from './playlist-end-screen';\n\nexport interface VideoItem {\n imageUrl: string;\n title: string;\n playbackId: string;\n adTagUrl: string | (() => string) | (() => Promise<string>); // NOTE: Consider making this and possibly other props optional (CJP)\n}\n\nexport type PlaylistVideos = VideoItem[];\n\nexport interface PlaylistProps extends Omit<MuxPlayerProps, 'playbackId' | 'adTagUrl'> {\n videoList: PlaylistVideos;\n}\n\nconst MuxNewsPlayer = ({ videoList, ...props }: PlaylistProps) => {\n const mediaElRef = useRef<any>(null);\n const [currentIndex, setCurrentIndex] = useState(0);\n\n const [currentAdTagUrlString, setCurrentAdTagUrlString] = useState(\n typeof videoList[currentIndex]?.adTagUrl === 'string' ? videoList[currentIndex]?.adTagUrl : undefined\n );\n\n useEffect(() => {\n setCurrentIndex(0);\n }, [videoList]);\n\n useEffect(() => {\n const videoAdTagUrl = videoList[currentIndex]?.adTagUrl;\n if (typeof videoAdTagUrl === 'string') {\n setCurrentAdTagUrlString(videoAdTagUrl);\n } else if (typeof videoAdTagUrl === 'function') {\n const adTagUrlFnReturnVal = videoAdTagUrl();\n if (typeof adTagUrlFnReturnVal === 'string') {\n setCurrentAdTagUrlString(adTagUrlFnReturnVal);\n } else if (typeof adTagUrlFnReturnVal?.then === 'function') {\n setCurrentAdTagUrlString(undefined);\n adTagUrlFnReturnVal.then(setCurrentAdTagUrlString);\n }\n }\n }, [currentIndex]);\n\n const [endScreenVisible, setEndScreenVisible] = useState(false);\n const [playerKey, setPlayerKey] = useState(0);\n\n function selectVideo(index: number) {\n setEndScreenVisible(false);\n setCurrentAdTagUrlString(undefined);\n setCurrentIndex(index);\n setTimeout(() => {\n try {\n mediaElRef.current.play();\n } catch {\n // Ignore AbortError: The play() request was interrupted by a call to pause()\n }\n }, 200);\n }\n\n return (\n <MuxPlayer\n theme={NewsTheme}\n style={{ aspectRatio: '16/9' }}\n preferPlayback=\"mse\"\n maxResolution=\"2160p\"\n minResolution=\"540p\"\n renditionOrder=\"desc\"\n metadata={{\n video_title: videoList[currentIndex].title,\n }}\n {...props}\n ref={mediaElRef}\n key={`player-${playerKey}`}\n playbackId={currentAdTagUrlString ? videoList[currentIndex].playbackId : undefined}\n adTagUrl={currentAdTagUrlString}\n onEnded={(event) => {\n if (currentIndex < videoList.length - 1) {\n setEndScreenVisible(true);\n } else {\n setCurrentIndex(0);\n setPlayerKey((prev) => prev + 1);\n }\n props.onEnded?.(event);\n }}\n >\n <PlaylistEndScreen\n currentIndex={currentIndex}\n relatedVideos={videoList}\n visible={endScreenVisible}\n selectVideoCallback={selectVideo}\n />\n </MuxPlayer>\n );\n};\n\nexport default MuxNewsPlayer;\n", "import React, { useEffect, useState } from 'react';\nimport { VideoItem } from '.';\nimport style from './playlist-end-screen.css';\n\ntype PlaylistEndScreenProps = {\n currentIndex: number;\n relatedVideos: VideoItem[];\n visible: boolean;\n selectVideoCallback: (index: number) => void;\n};\n\nconst PlaylistEndScreen = ({\n currentIndex = 0,\n relatedVideos,\n visible,\n selectVideoCallback,\n}: PlaylistEndScreenProps) => {\n const [count, setCount] = useState(3);\n const video = relatedVideos[currentIndex];\n\n useEffect(() => {\n if (!visible) {\n setCount(3);\n return;\n }\n\n // For the countdown, simply select the \"next\" video. If at the end, cycle back to the 0th video.\n if (count < 0) {\n const nextIndex = (currentIndex + 1) % relatedVideos.length;\n selectVideoCallback(nextIndex);\n return;\n }\n const timer = setInterval(() => {\n setCount((prev) => Math.max(prev - 1, -1));\n }, 1000);\n\n return () => clearInterval(timer);\n }, [count, visible]);\n\n return (\n <>\n <style>{style}</style>\n <div className=\"playlist\" style={{ display: visible ? 'grid' : 'none' }}>\n <div className=\"overlay\" style={{ display: visible ? 'grid' : 'none' }} />\n <div className=\"post-video-section\" style={{ display: visible ? 'grid' : 'none', zIndex: 99 }}>\n <div className=\"video-section\">\n <div className=\"video-container\">\n <h2 className=\"title\">Video</h2>\n <div className=\"video-wrapper\">\n <img className=\"video-thumbnail\" src={video.imageUrl} alt={video.title} />\n <div className=\"countdown-overlay\">\n <svg className=\"countdown-ring\" width=\"50\" height=\"50\">\n <circle cx=\"25\" cy=\"25\" r=\"22\" className=\"circle-background\" />\n <circle\n cx=\"25\"\n cy=\"25\"\n r=\"22\"\n className=\"circle-progress\"\n style={{\n strokeDasharray: '138',\n strokeDashoffset: `${(count / 3) * 138}`,\n }}\n />\n </svg>\n <span className=\"count-text\">{count}</span>\n </div>\n </div>\n <p className=\"video-title\">{video.title}</p>\n </div>\n </div>\n <hr />\n <div className=\"related-videos-section\">\n <h3 className=\"related-title\">Related Videos</h3>\n <ul className=\"related-list\">\n {relatedVideos.map((relatedVideo, index) => (\n <li key={index}>\n <button className=\"related-item\" onClick={() => selectVideoCallback(index)}>\n <img className=\"related-thumbnail\" src={relatedVideo.imageUrl} alt={relatedVideo.title} />\n <p className=\"related-text\">{relatedVideo.title}</p>\n </button>\n </li>\n ))}\n </ul>\n </div>\n </div>\n </div>\n </>\n );\n};\n\nexport default PlaylistEndScreen;\n", "/* Main Playlist Container */\n.playlist {\n /* Ensure it wraps on smaller screens */\n display: inline;\n position: relative;\n background-color: #12121263;\n z-index: 2;\n top: 0;\n position: absolute;\n width: 100%;\n height: 100%;\n}\n\n@media (min-width: 1336px) {\n .playlist {\n align-items: center;\n justify-content: center;\n }\n}\n\n.overlay {\n position: absolute;\n width: 100%;\n height: 100%;\n background: black;\n opacity: 0.5;\n z-index: 1;\n}\n\n.post-video-section {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n padding: 1.5rem 2rem;\n position: relative;\n gap: 1rem;\n padding: 1.5rem 2rem;\n height: max-content;\n box-sizing: border-box;\n z-index: 2;\n max-width: 1200px;\n}\n\n.post-video-section hr {\n border: none;\n background: rgba(255, 255, 255, 0.5);\n height: 100%;\n width: 1px;\n}\n\n/* Video Section */\n.video-section {\n flex: 2;\n}\n\n.video-container {\n position: relative;\n}\n\n.title {\n font-size: 2.5rem;\n font-weight: 500;\n line-height: 3rem;\n margin: 0;\n margin-bottom: 1rem;\n}\n\n.video-wrapper {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n\n.video-container > .video-title {\n font-size: 1.3rem;\n font-weight: 600;\n}\n\n.video-thumbnail {\n width: 100%;\n display: block;\n}\n\n.video-title {\n font-size: 1rem;\n margin-top: 0.5rem;\n cursor: pointer;\n color: #ffffff;\n text-decoration: none;\n line-height: 1.4;\n /* Adjusted for better readability */\n word-wrap: break-word;\n font-weight: 500;\n margin-bottom: 0;\n}\n\n.video-title:hover {\n text-decoration: underline;\n}\n\n/* Countdown Timer */\n.countdown-overlay {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 3.75rem;\n height: 3.75rem;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n.countdown-ring {\n position: absolute;\n}\n\n.circle-background {\n fill: none;\n stroke: rgba(255, 255, 255, 0.2);\n stroke-width: 0.25rem;\n}\n\n.circle-progress {\n fill: none;\n stroke: #00a3dd;\n stroke-width: 0.25rem;\n stroke-linecap: round;\n transition: stroke-dashoffset 1s linear;\n}\n\n.count-text {\n position: absolute;\n font-size: 1rem;\n font-weight: bold;\n color: #ffffff;\n}\n\n/* Related Videos */\n.related-videos-section {\n flex: 1;\n width: 100%;\n}\n\n.related-title {\n font-size: 1.125rem;\n font-weight: bold;\n margin: 0;\n line-height: 2rem;\n}\n\n.related-list {\n list-style: none;\n padding: 0;\n margin: 0;\n}\n\n.related-item {\n display: flex;\n align-items: start;\n border-bottom: 1px solid rgba(255, 255, 255, 0.5);\n width: 100%;\n gap: 0.5rem;\n padding: 0.5rem 0;\n border-radius: 0;\n background: none;\n}\n\n.related-item:hover {\n background: none;\n}\n.related-thumbnail {\n width: 7rem;\n object-fit: cover;\n aspect-ratio: 16 / 9;\n}\n\n.related-text {\n font-size: 0.9rem;\n color: white;\n line-height: 1.4;\n word-wrap: break-word;\n max-width: 100%;\n margin-top: 0.25rem;\n display: block;\n}\n\n.related-text:hover {\n text-decoration: underline;\n}\n\n/* Responsive */\n\n@media (max-width: 768px) {\n .post-video-section {\n grid-template-columns: 1fr;\n margin: auto;\n }\n\n .post-video-section h2 {\n display: none;\n }\n\n hr {\n display: none;\n }\n\n .video-section {\n width: 60%;\n margin: auto;\n }\n\n .related-videos-section {\n display: none;\n }\n}\n"],
"mappings": "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",
"names": ["React", "useEffect", "useRef", "useState", "MuxPlayer", "NewsTheme", "React", "useEffect", "useState", "playlist_end_screen_default", "PlaylistEndScreen", "currentIndex", "relatedVideos", "visible", "selectVideoCallback", "count", "setCount", "useState", "video", "useEffect", "nextIndex", "timer", "prev", "React", "playlist_end_screen_default", "relatedVideo", "index", "MuxNewsPlayer", "videoList", "props", "_a", "_b", "mediaElRef", "useRef", "currentIndex", "setCurrentIndex", "useState", "currentAdTagUrlString", "setCurrentAdTagUrlString", "useEffect", "videoAdTagUrl", "adTagUrlFnReturnVal", "endScreenVisible", "setEndScreenVisible", "playerKey", "setPlayerKey", "selectVideo", "index", "React", "MuxPlayer", "NewsTheme", "event", "prev", "playlist_end_screen_default", "index_default"]
}