UNPKG

@oplayer/plugins

Version:
160 lines (122 loc) 4.96 kB
import type { Player, PlayerPlugin, Source } from '@oplayer/core' import { loadSDK, isIOS } from '@oplayer/core' const IS_CHROME = !!globalThis.chrome const ICON = `<svg viewBox="0 0 1024 1024" style="scale: 0.9;"><path d="M895.66 128H128a85.44 85.44 0 0 0-85.44 85.44v127.84H128v-127.84h767.66v597.12H597.28V896H896a85.44 85.44 0 0 0 85.44-85.44V213.44A85.44 85.44 0 0 0 896 128zM42.56 767.16v127.84h127.82a127.82 127.82 0 0 0-127.82-127.84z m0-170.56V682a213.26 213.26 0 0 1 213.28 213.32v0.68h85.44a298.38 298.38 0 0 0-298-298.72h-0.66z m0-170.54v85.44c212-0.2 384 171.5 384.16 383.5v1h85.44c-0.92-258.92-210.68-468.54-469.6-469.28z"></path></svg>` // TODO: Sync remote controller state export interface ChromeCastOptions { autoJoinPolicy?: chrome.cast.AutoJoinPolicy language?: string | undefined receiverApplicationId?: string | undefined resumeSavedSession?: boolean | undefined /** The following flag enables Cast Connect(requires Chrome 87 or higher) */ androidReceiverCompatible?: boolean | undefined } class Chromecast implements PlayerPlugin { readonly name = 'oplayer-plugin-chromecast' readonly version = __VERSION__ public player: Player protected _player?: cast.framework.RemotePlayer constructor(public options?: ChromeCastOptions) {} apply(player: Player) { if (!this.canPlay()) return this.player = player this.registerUI() return this } get cast() { return cast.framework.CastContext.getInstance() } get castSessionMedia() { return this.cast.getCurrentSession()?.getSessionObj().media[0] } get isCastConnected() { return this.cast.getCastState() === cast.framework.CastState.CONNECTED } get device() { return this.cast.getCurrentSession()?.getCastDevice() } hasActiveCastSession(source: Source | undefined | null) { const contentId = this.castSessionMedia?.media?.contentId return contentId === source?.src } canPlay() { return IS_CHROME && !isIOS } async __requestChromeCast() { if (!this._player) { this._player = new cast.framework.RemotePlayer() new cast.framework.RemotePlayerController(this._player) } if (this.hasActiveCastSession(this.player.options.source)) { return } this.cast.setOptions({ receiverApplicationId: window.chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, resumeSavedSession: true, androidReceiverCompatible: true, ...this.options }) const errorCode = await this.cast.requestSession() if (errorCode) { throw new Error(`Chrome Cast Error Code: ${errorCode}`) } return this.cast.getCurrentSession()?.loadMedia(this.__buildRequest()) } __buildRequest() { const { source, isLive } = this.player.options const mediaInfo = new chrome.cast.media.MediaInfo( source.src, this.player.loader?.key == 'hls' ? 'application/x-mpegurl' : 'video/mp4' ) ;(mediaInfo as any).contentUrl = source.src mediaInfo.streamType = isLive ? chrome.cast.media.StreamType.LIVE : chrome.cast.media.StreamType.BUFFERED const metadata = new chrome.cast.media.GenericMediaMetadata() if (source.title) metadata.title = source.title if (source.poster) metadata.images = [{ url: source.poster, height: null, width: null }] mediaInfo.metadata = metadata const subtitles = this.player.context.ui?.config.subtitle?.source as any[] | undefined if (subtitles) { mediaInfo.tracks = subtitles.map((sub, id) => { const track = new chrome.cast.media.Track(id, chrome.cast.media.TrackType.TEXT) track.name = sub.name track.trackContentId = sub.src track.trackContentType = sub.type || 'text/vtt' //TODO: url match track.language = sub.language || sub.name track.subtype = chrome.cast.media.TextTrackType.CAPTIONS return track }) } const request = new chrome.cast.media.LoadRequest(mediaInfo) request.autoplay = this.player.isPlaying request.currentTime = this.player.currentTime return request } async _loadCast() { if (!!window.cast?.framework) return await loadSDK('https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1', 'cast') await customElements.whenDefined('google-cast-launcher') } async start() { try { await this._loadCast() const errorCode = await this.__requestChromeCast() if (errorCode) { throw new Error(`Chrome Cast Error Code: ${errorCode}`) } } catch (error) { this.player.emit('cast-error', error) } } registerUI() { if (!this.player.context.ui) return const { menu, icons } = this.player.context.ui menu?.register({ name: this.player.locales.get('Chromecast'), position: 'top', icon: icons.chromecast || ICON, onClick: () => this.start() }) } } export default Chromecast