parent
60dedbb509
commit
1bc47d33b1
@ -0,0 +1,177 @@ |
|||||||
|
/* eslint-disable no-undef */ |
||||||
|
/* eslint-disable max-classes-per-file */ |
||||||
|
/** |
||||||
|
* Constants of states for media playback |
||||||
|
* @enum {string} |
||||||
|
*/ |
||||||
|
const PLAYER_STATE = { |
||||||
|
ERROR: 'ERROR', |
||||||
|
IDLE: 'IDLE', |
||||||
|
LOADED: 'LOADED', |
||||||
|
LOADING: 'LOADING', |
||||||
|
PAUSED: 'PAUSED', |
||||||
|
PLAYING: 'PLAYING', |
||||||
|
STOPPED: 'STOPPED', |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Cast player object |
||||||
|
* Main variables: |
||||||
|
* - PlayerHandler object for handling media playback |
||||||
|
* - Cast player variables for controlling Cast mode media playback |
||||||
|
* - Current media variables for transition between Cast and local modes |
||||||
|
* @struct @constructor |
||||||
|
*/ |
||||||
|
export class CastPlayer { |
||||||
|
constructor(source) { |
||||||
|
/** @type {PlayerHandler} Delegation proxy for media playback */ |
||||||
|
this.playerHandler = new PlayerHandler(this) |
||||||
|
|
||||||
|
/** @type {PLAYER_STATE} A state for media playback */ |
||||||
|
this.playerState = PLAYER_STATE.IDLE |
||||||
|
|
||||||
|
/* Cast player variables */ |
||||||
|
/** @type {cast.framework.RemotePlayer} */ |
||||||
|
this.remotePlayer = null |
||||||
|
/** @type {cast.framework.RemotePlayerController} */ |
||||||
|
this.remotePlayerController = null |
||||||
|
|
||||||
|
this.src = source |
||||||
|
} |
||||||
|
|
||||||
|
initializeCastPlayer() { |
||||||
|
const options = {} |
||||||
|
|
||||||
|
// Set the receiver application ID to your own (created in the
|
||||||
|
// Google Cast Developer Console), or optionally
|
||||||
|
// use the chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID
|
||||||
|
options.receiverApplicationId = chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID |
||||||
|
|
||||||
|
// Auto join policy can be one of the following three:
|
||||||
|
// ORIGIN_SCOPED - Auto connect from same appId and page origin
|
||||||
|
// TAB_AND_ORIGIN_SCOPED - Auto connect from same appId, page origin, and tab
|
||||||
|
// PAGE_SCOPED - No auto connect
|
||||||
|
options.autoJoinPolicy = chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED |
||||||
|
options.androidReceiverCompatible = false |
||||||
|
|
||||||
|
cast.framework.CastContext.getInstance().setOptions(options) |
||||||
|
|
||||||
|
// const credentialsData = new chrome.cast.CredentialsData('{"userId": "abc"}')
|
||||||
|
// cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData)
|
||||||
|
|
||||||
|
this.remotePlayer = new cast.framework.RemotePlayer() |
||||||
|
this.remotePlayerController = new cast.framework.RemotePlayerController(this.remotePlayer) |
||||||
|
this.remotePlayerController.addEventListener( |
||||||
|
cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED, |
||||||
|
this.switchPlayer.bind(this), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* PlayerHandler and setup functions |
||||||
|
*/ |
||||||
|
switchPlayer() { |
||||||
|
this.playerState = PLAYER_STATE.IDLE |
||||||
|
if (cast && cast.framework && this.remotePlayer.isConnected) { |
||||||
|
this.setupRemotePlayer() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the PlayerHandler target to use the remote player |
||||||
|
*/ |
||||||
|
setupRemotePlayer() { |
||||||
|
const castSession = cast.framework.CastContext.getInstance().getCurrentSession() |
||||||
|
|
||||||
|
// This object will implement PlayerHandler callbacks with
|
||||||
|
// remotePlayerController, and makes necessary UI updates specific
|
||||||
|
// to remote playback
|
||||||
|
const playerTarget = {} |
||||||
|
|
||||||
|
playerTarget.play = function () { |
||||||
|
if (this.remotePlayer.isPaused) { |
||||||
|
this.remotePlayerController.playOrPause() |
||||||
|
} |
||||||
|
}.bind(this) |
||||||
|
|
||||||
|
playerTarget.pause = function () { |
||||||
|
if (!this.remotePlayer.isPaused) { |
||||||
|
this.remotePlayerController.playOrPause() |
||||||
|
} |
||||||
|
}.bind(this) |
||||||
|
|
||||||
|
playerTarget.stop = function () { |
||||||
|
this.remotePlayerController.stop() |
||||||
|
}.bind(this) |
||||||
|
|
||||||
|
playerTarget.load = function () { |
||||||
|
const mediaInfo = new chrome.cast.media.MediaInfo(this.src, 'video/mp4') |
||||||
|
|
||||||
|
mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata() |
||||||
|
mediaInfo.metadata.metadataType = chrome.cast.media.MetadataType.GENERIC |
||||||
|
mediaInfo.metadata.title = 'title' |
||||||
|
mediaInfo.metadata.images = [{ url: 'https://s3.eu-central-1.amazonaws.com/img.hromadske.ua/posts/224808/opng/medium.jpg' }] |
||||||
|
const request = new chrome.cast.media.LoadRequest(mediaInfo) |
||||||
|
request.credentials = 'user-credentials' |
||||||
|
request.atvCredentials = 'atv-user-credentials' |
||||||
|
castSession.loadMedia(request).then( |
||||||
|
this.playerHandler.loaded.bind(this.playerHandler), |
||||||
|
(errorCode) => { |
||||||
|
this.playerState = PLAYER_STATE.ERROR |
||||||
|
}, |
||||||
|
) |
||||||
|
}.bind(this) |
||||||
|
|
||||||
|
this.playerHandler.setTarget(playerTarget) |
||||||
|
|
||||||
|
this.playerHandler.play() |
||||||
|
} |
||||||
|
} |
||||||
|
/** |
||||||
|
* PlayerHandler |
||||||
|
*/ |
||||||
|
class PlayerHandler { |
||||||
|
constructor(castPlayer) { |
||||||
|
this.target = {} |
||||||
|
|
||||||
|
this.setTarget = function (target) { |
||||||
|
this.target = target |
||||||
|
} |
||||||
|
|
||||||
|
this.play = function () { |
||||||
|
if (castPlayer.playerState !== PLAYER_STATE.PLAYING |
||||||
|
&& castPlayer.playerState !== PLAYER_STATE.PAUSED |
||||||
|
&& castPlayer.playerState !== PLAYER_STATE.LOADED) { |
||||||
|
this.load(castPlayer.src) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
this.target.play() |
||||||
|
castPlayer.playerState = PLAYER_STATE.PLAYING |
||||||
|
} |
||||||
|
|
||||||
|
this.pause = function () { |
||||||
|
if (castPlayer.playerState !== PLAYER_STATE.PLAYING) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
this.target.pause() |
||||||
|
castPlayer.playerState = PLAYER_STATE.PAUSED |
||||||
|
} |
||||||
|
|
||||||
|
this.stop = function () { |
||||||
|
this.pause() |
||||||
|
castPlayer.playerState = PLAYER_STATE.STOPPED |
||||||
|
} |
||||||
|
|
||||||
|
this.load = function (src) { |
||||||
|
castPlayer.playerState = PLAYER_STATE.LOADING |
||||||
|
|
||||||
|
this.target.load(src) |
||||||
|
} |
||||||
|
|
||||||
|
this.loaded = function () { |
||||||
|
castPlayer.playerState = PLAYER_STATE.LOADED |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,70 @@ |
|||||||
|
import { |
||||||
|
Fragment, |
||||||
|
memo, |
||||||
|
useEffect, |
||||||
|
useRef, |
||||||
|
useState, |
||||||
|
} from 'react' |
||||||
|
|
||||||
|
import { readToken } from 'helpers' |
||||||
|
|
||||||
|
import { API_ROOT } from 'config' |
||||||
|
import { usePageParams } from 'hooks/usePageParams' |
||||||
|
|
||||||
|
import { Container } from './styled' |
||||||
|
import { CastPlayer } from './CastVideos' |
||||||
|
|
||||||
|
type Props = { |
||||||
|
src?: string, |
||||||
|
} |
||||||
|
|
||||||
|
export const ChromeCast = memo(({ src } : Props) => { |
||||||
|
const [isCastAvailable, setIsCastAvailable] = useState(false) |
||||||
|
|
||||||
|
const { profileId: matchId, sportType } = usePageParams() |
||||||
|
|
||||||
|
const containerRef = useRef<HTMLDivElement | null>(null) |
||||||
|
const GoogleCastLauncher = (document as any).createElement('google-cast-launcher') |
||||||
|
GoogleCastLauncher.setAttribute('id', 'castbutton') |
||||||
|
|
||||||
|
const baseUrl = src ?? `${API_ROOT}/video/chromecast/stream/${sportType}/${matchId}.m3u8` |
||||||
|
const urlWithToken = (/\d.m3u8/.test(baseUrl)) ? `${baseUrl}?access_token=${readToken()}` : baseUrl |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if ( |
||||||
|
containerRef.current |
||||||
|
&& GoogleCastLauncher |
||||||
|
&& containerRef.current.childNodes.length < 1 |
||||||
|
) { |
||||||
|
(containerRef.current as any).appendChild(GoogleCastLauncher) |
||||||
|
} |
||||||
|
}, [GoogleCastLauncher]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const script = document.createElement('script') |
||||||
|
|
||||||
|
script.src = '//www.gstatic.com/cv/js/sender/v1/cast_sender.js' |
||||||
|
script.async = true |
||||||
|
|
||||||
|
document.body.appendChild(script) |
||||||
|
|
||||||
|
const castPlayer = new CastPlayer(urlWithToken); |
||||||
|
(window as any).__onGCastApiAvailable = (isAvailable: boolean) => { |
||||||
|
if (isAvailable) { |
||||||
|
castPlayer.initializeCastPlayer() |
||||||
|
setIsCastAvailable(true) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return () => { |
||||||
|
document.body.removeChild(script) |
||||||
|
setIsCastAvailable(false) |
||||||
|
} |
||||||
|
}, [urlWithToken]) |
||||||
|
|
||||||
|
return ( |
||||||
|
<Fragment> |
||||||
|
{isCastAvailable && <Container ref={containerRef} />} |
||||||
|
</Fragment> |
||||||
|
) |
||||||
|
}) |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
import styled, { css } from 'styled-components' |
||||||
|
|
||||||
|
import { isMobileDevice } from 'config/userAgent' |
||||||
|
|
||||||
|
export const Container = styled.div` |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
margin-left: 25px; |
||||||
|
height: 24px; |
||||||
|
width: 24px; |
||||||
|
|
||||||
|
& #castbutton:hover { |
||||||
|
--disconnected-color: #FFFFFF; |
||||||
|
} |
||||||
|
|
||||||
|
${isMobileDevice ? css` |
||||||
|
margin-left: 15px; |
||||||
|
` : ''}
|
||||||
|
` |
||||||
Loading…
Reference in new issue