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