import { AudioTrack, HTMLMediaElement, TextTrack, VideoPlayer, VideoTrack } from '@amazon-devices/react-native-w3cmedia/dist/headless';
import {
Event,
EventListener,
TrackEvent,
} from '@amazon-devices/react-native-w3cmedia/dist/headless';
import { ShakaPlayer } from './shakaplayer/ShakaPlayer';
import {
IHttpHeader,
IPlayerSessionId,
IPlayerSessionLoadParams,
IPlayerSessionMediaInfo,
IPlayerSessionPosition,
IPlayerSessionState,
IPlayerSessionStatus,
ITimeRange,
ITrackType,
IViewHandle,
} from '@amazon-devices/kepler-player-server';
import {
IPlayerServer,
IPlayerServerHandler,
IPlayerServerFactory,
PlayerServerFactory,
} from '@amazon-devices/kepler-player-server';
class PlayerServerHandler implements IPlayerServerHandler {
#playerService: PlayerService;
constructor(playerService: PlayerService) {
console.log('[PlayerService] VegaPlayerServerHandler ctor');
this.#playerService = playerService;
}
handleLoad(mediaInfo: IPlayerSessionMediaInfo, loadParams?: IPlayerSessionLoadParams, sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleLoadが呼び出されました。引数:', {
mediaInfo,
loadParams,
sessionId,
});
this.#playerService?.onLoad(mediaInfo, loadParams, sessionId);
}
handleUnload(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleUnloadが呼び出されました。引数:', {
sessionId
});
this.#playerService?.onUnload();
}
handleSetVideoView(handle: IViewHandle, sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleSetVideoViewが呼び出されました。引数:', {
handle,
sessionId
});
this.#playerService?.onSurfaceViewCreated(handle.handle);
}
handleClearVideoView(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleClearVideoViewが呼び出されました。sessionId:', sessionId);
this.#playerService?.onSurfaceViewDestroyed();
}
handleSetTextView(handle: IViewHandle, sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleSetVideoViewが呼び出されました。引数:', {
handle,
sessionId
});
this.#playerService?.onCaptionViewCreated(handle.handle);
}
handleClearTextView(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleClearTextViewが呼び出されました。引数:', {
sessionId
});
this.#playerService?.onCaptionViewDestroyed(handle.handle);
}
handlePlay(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handlePlayが呼び出されました。sessionId:', sessionId);
this.#playerService?.handlePlay();
}
handlePause(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handlePauseが呼び出されました。sessionId:', sessionId);
this.#playerService?.handlePause();
}
handleSeek(position: number, isRelative?: boolean, sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleSeekが呼び出されました。引数:', {
position,
sessionId
});
this.#playerService?.handleSeek(position);
}
handleSetMute(isMuted: boolean, sessionId?: IPlayerSessionId): void {
console.debug('[PlayerService] handleSetMuteが呼び出されました。引数:', {
sessionId
});
this.#playerService?.handleSetMute(isMuted);
}
handleSetPlaybackRate(playbackRate: number, sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleSetPlaybackRateが呼び出されました。引数:', {
sessionId
});
}
handleSetVolume(volume: number, sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleSetVolumeが呼び出されました。引数:', {
volume,
sessionId
});
this.#playerService?.handleSetVolume(volume);
}
handleSetActiveTrack(trackType: ITrackType, trackId: string, sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleSetActiveTrackが呼び出されました。引数:', {
trackType,
trackId,
sessionId
});
this.#playerService.handleSetActiveTrack(String(trackType), trackId);
}
handleGetCurrentPosition(sessionId?: IPlayerSessionId): number {
console.log('[PlayerService] handleGetCurrentPositionが呼び出されました。引数:', {
sessionId
});
return this.#playerService.getCurrentPlaybackPosition().position;
}
handleMessage(message: any, sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleMessageが呼び出されました。引数:', {
sessionId
});
}
handleStartBufferedRangesUpdates(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleStartBufferedRangesUpdatesが呼び出されました。引数:', {
sessionId
});
}
handleStopBufferedRangesUpdates(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleStopBufferedRangesUpdatesが呼び出されました。引数:', {
sessionId
});
}
handleStartStatusUpdates(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleStartStatusUpdatesが呼び出されました。引数:', {
sessionId
});
}
handleStopStatusUpdates(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleStopStatusUpdatesが呼び出されました。引数:', {
sessionId
});
}
handleStartTrackUpdates(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleStartTrackUpdatesが呼び出されました。引数:', {
sessionId
});
}
handleStopTrackUpdates(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleStopTrackUpdatesが呼び出されました。引数:', {
sessionId
});
}
handleStartMessageUpdates(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleStartMessageUpdatesが呼び出されました。引数:', {
sessionId
});
}
handleStopMessageUpdates(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleStopMessageUpdatesが呼び出されました。引数:', {
sessionId
});
}
handleStartErrorUpdates(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleStartErrorUpdatesが呼び出されました。引数:', {
sessionId
});
}
handleStopErrorUpdates(sessionId?: IPlayerSessionId): void {
console.log('[PlayerService] handleStopErrorUpdatesが呼び出されました。引数:', {
sessionId
});
}
}
class PlayerService {
#playerServerFactory: IPlayerServerFactory | undefined;
#playerServer: IPlayerServer | undefined;
#playerServerHandler: IPlayerServerHandler | undefined;
#msePlayer: ShakaPlayer | undefined;
#videoPlayer: VideoPlayer | undefined;
#serviceComponentId: string = "com.yourcompany.kepler.headlessjsmediaplayer.service";
#activeSurfaceHandle: string | undefined;
#activeCaptionHandle: string | undefined;
#activeSessionId: IPlayerSessionId | undefined;
#hasError: boolean = false;
#AUTOPLAY: boolean = true;
#initializeVideoPlayer = async (): Promise<void> => {
console.log('[PlayerService] initializeVideoPlayer');
this.#videoPlayer = new VideoPlayer();
// @ts-ignore
global.gmedia = this.#videoPlayer;
await this.#videoPlayer.initialize();
this.#setUpEventListeners();
this.#videoPlayer.autoplay = this.#AUTOPLAY;
this.#initialiseMsePlayer();
};
#initialiseMsePlayer = () => {
console.log('[PlayerService] initialiseMsePlayer');
if (this.#videoPlayer !== undefined) {
this.#msePlayer = new ShakaPlayer(this.#videoPlayer as HTMLMediaElement, {
secure: false, // 再生をセキュアモードまたは非セキュアモードで実行
abrEnabled: true, // アダプティブビットレート(ABR)の切り替えを有効化
abrMaxWidth: 3840, // ABRで許容される最大幅
abrMaxHeight: 2160, // ABRで許容される最大高さ
startPosition: 0
});
}
};
#setUpEventListeners = (): void => {
console.log('[PlayerService] setUpEventListeners');
this.#videoPlayer?.addEventListener('play', this.#onPlay);
this.#videoPlayer?.addEventListener('pause', this.#onPause);
this.#videoPlayer?.addEventListener('seeked', this.#onSeeked);
this.#videoPlayer?.addEventListener('ended', this.#onEnded);
this.#videoPlayer?.addEventListener('error', this.#onError);
this.#videoPlayer?.audioTracks.addEventListener('addtrack', this.#onAudioTrackAdded);
this.#videoPlayer?.videoTracks.addEventListener('addtrack', this.#onVideoTrackAdded);
this.#videoPlayer?.textTracks.addEventListener('addtrack', this.#onTextTrackAdded);
this.#videoPlayer?.audioTracks.addEventListener('removetrack', this.#onAudioTrackRemoved);
this.#videoPlayer?.videoTracks.addEventListener('removetrack', this.#onVideoTrackRemoved);
this.#videoPlayer?.textTracks.addEventListener('removetrack', this.#onTextTrackRemoved);
(this.#videoPlayer as HTMLMediaElement)?.addEventListener('waiting', this.#updateBufferedRanges);
(this.#videoPlayer as HTMLMediaElement)?.addEventListener('canplay', this.#updateBufferedRanges);
};
#removeEventListeners = (): void => {
console.log('[PlayerService] removeEventListeners');
this.#videoPlayer?.removeEventListener('play', this.#onPlay);
this.#videoPlayer?.removeEventListener('pause', this.#onPause);
this.#videoPlayer?.removeEventListener('seeked', this.#onSeeked);
this.#videoPlayer?.removeEventListener('ended', this.#onEnded);
this.#videoPlayer?.removeEventListener('error', this.#onError);
this.#videoPlayer?.audioTracks.removeEventListener('addtrack', this.#onAudioTrackAdded);
this.#videoPlayer?.videoTracks.removeEventListener('addtrack', this.#onVideoTrackAdded);
this.#videoPlayer?.textTracks.removeEventListener('addtrack', this.#onTextTrackAdded);
this.#videoPlayer?.audioTracks.removeEventListener('removetrack', this.#onAudioTrackRemoved);
this.#videoPlayer?.videoTracks.removeEventListener('removetrack', this.#onVideoTrackRemoved);
this.#videoPlayer?.textTracks.removeEventListener('removetrack', this.#onTextTrackRemoved);
(this.#videoPlayer as HTMLMediaElement)?.removeEventListener('waiting', this.#updateBufferedRanges);
(this.#videoPlayer as HTMLMediaElement)?.removeEventListener('canplay', this.#updateBufferedRanges);
};
#onPlay: EventListener = (event?: Event) => {
console.log(`[PlayerService] onPlay ${event?.type}`);
this.updateStatus(this.#activeSessionId);
}
#onPause: EventListener = (event?: Event) => {
console.log('[PlayerService] onPause');
this.updateStatus(this.#activeSessionId);
}
#onSeeked: EventListener = (event?: Event) => {
console.log('[PlayerService] onSeeked');
}
#onEnded: EventListener = (event?: Event) => {
console.log('[PlayerService] onEnded');
this.updateStatus(this.#activeSessionId);
this.onUnload();
}
#onError: EventListener = (event?: Event) => {
console.log(`[PlayerService] onErrorが呼び出されました。コード:{}、メッセージ:{}`,
this.#videoPlayer?.error.code, this.#videoPlayer?.error.message);
this.#hasError = true;
};
#onAudioTrackAdded: EventListener = (event?: Event) => {
if (event instanceof TrackEvent) {
let track = (event as TrackEvent).track;
if (track !== undefined) {
this.#playerServer?.addTrack({
id: track.id,
type: "AUDIO",
kind: track.kind,
label: track.label,
language: track.language,
enabled: (track as AudioTrack).enabled
}, this.#activeSessionId);
// 必要に応じてその他の操作を実行します。
} else {
console.error("オーディオトラックが未定義です。");
}
}
}
#onVideoTrackAdded: EventListener = (event?: Event) => {
if (event instanceof TrackEvent) {
let track = (event as TrackEvent).track;
if (track !== undefined) {
this.#playerServer?.addTrack({
id: track.id,
type: "VIDEO",
kind: track.kind,
label: track.label,
language: track.language,
enabled: (track as VideoTrack).selected
}, this.#activeSessionId);
// 必要に応じてその他の操作を実行します。
} else {
console.error("ビデオトラックが未定義です。");
}
}
}
#onTextTrackAdded: EventListener = (event?: Event) => {
if (event instanceof TrackEvent) {
let track = (event as TrackEvent).track;
if (track !== undefined) {
this.#playerServer?.addTrack({
id: track.id,
type: "TEXT",
kind: track.kind,
label: track.label,
language: track.language,
mode: (track as TextTrack).mode
}, this.#activeSessionId);
// 必要に応じてその他の操作を実行します。
} else {
console.error("テキストトラックが未定義です。");
}
}
}
#onAudioTrackRemoved: EventListener = (event?: Event) => {
if (event instanceof TrackEvent) {
let track = (event as TrackEvent).track;
if (track !== undefined) {
this.#playerServer?.removeTrack({
id: track.id,
type: "AUDIO",
kind: track.kind,
label: track.label,
language: track.language,
enabled: (track as AudioTrack).enabled
}, this.#activeSessionId);
// 必要に応じてその他の操作を実行します。
} else {
console.error("オーディオトラックが未定義です。");
}
}
}
#onVideoTrackRemoved: EventListener = (event?: Event) => {
if (event instanceof TrackEvent) {
let track = (event as TrackEvent).track;
if (track !== undefined) {
this.#playerServer?.removeTrack({
id: track.id,
type: "VIDEO",
kind: track.kind,
label: track.label,
language: track.language,
enabled: (track as VideoTrack).selected
}, this.#activeSessionId);
// 必要に応じてその他の操作を実行します。
} else {
console.error("ビデオトラックが未定義です。");
}
}
}
#onTextTrackRemoved: EventListener = (event?: Event) => {
if (event instanceof TrackEvent) {
let track = (event as TrackEvent).track;
if (track !== undefined) {
this.#playerServer?.removeTrack({
id: track.id,
type: "TEXT",
kind: track.kind,
label: track.label,
language: track.language,
mode: (track as TextTrack).mode
}, this.#activeSessionId);
// 必要に応じてその他の操作を実行します。
} else {
console.error("テキストトラックが未定義です。");
}
}
}
#updateBufferedRanges: EventListener = (event?: Event) => {
const result: Array<ITimeRange> = [];
const bufferedTimeRanges = this.#videoPlayer?.buffered;
if (bufferedTimeRanges === undefined) {
return;
}
for (let i = 0; i < bufferedTimeRanges.length; ++i) {
result.push({
start: bufferedTimeRanges.start(i),
end: bufferedTimeRanges.end(i)
});
}
this.#playerServer?.updateBufferedRanges(result, this.#activeSessionId);
}
#getPlaybackState = () : IPlayerSessionState => {
if (!this.#videoPlayer) return IPlayerSessionState.ENDED;
if (this.#hasError) return IPlayerSessionState.ERROR;
if (this.#videoPlayer.ended) return IPlayerSessionState.ENDED;
if (this.#videoPlayer.seeking) return IPlayerSessionState.SEEKING;
if (this.#videoPlayer.paused) {
return IPlayerSessionState.PAUSED;
}
return IPlayerSessionState.PLAYING;
}
updateStatus = (sessionId?: IPlayerSessionId) => {
console.debug(`[PlayerService] updateStatusが呼び出されました。sessionId:${sessionId !== undefined ? sessionId?.id: "undefined"}`);
const playerSessionStatus: IPlayerSessionStatus = {
sessionId: sessionId,
playbackState: this.#getPlaybackState(),
playbackRate: this.#videoPlayer?.playbackRate || 1,
isMuted: this.#videoPlayer?.muted || false,
volume: this.#videoPlayer?.volume || 0,
seekable: (this.#videoPlayer?.seekable.length || 0) > 0,
duration: this.#videoPlayer?.duration
}
this.#playerServer?.updateStatus([playerSessionStatus]);
}
getCurrentPlaybackPosition = () : IPlayerSessionPosition => {
return {
sessionId: this.#activeSessionId,
position: this.#videoPlayer?.currentTime ?? 0 as number,
} as IPlayerSessionPosition;
}
onSurfaceViewCreated = (surfaceHandle: string): void => {
console.log('[PlayerService] onSurfaceViewCreatedが呼び出されました。surfaceHandle:', surfaceHandle);
this.#activeSurfaceHandle = surfaceHandle;
this.#videoPlayer?.setSurfaceHandle(surfaceHandle);
};
onSurfaceViewDestroyed = (): void => {
console.log('[PlayerService] onSurfaceViewDestroyedが呼び出されました');
if (this.#activeSurfaceHandle !== undefined) {
this.#videoPlayer?.clearSurfaceHandle(this.#activeSurfaceHandle);
}
}
onCaptionViewCreated = (surfaceHandle: string): void => {
console.log('[PlayerService] onCaptionViewCreatedが呼び出されました。surfaceHandle:', surfaceHandle);
this.#activeCaptionHandle = surfaceHandle;
this.#videoPlayer?.setCaptionViewHandle(surfaceHandle);
};
onCaptionViewDestroyed = (): void => {
console.log('[PlayerService] onCaptionViewDestroyedが呼び出されました');
if (this.#activeCaptionHandle !== undefined) {
this.#videoPlayer?.clearCaptionViewHandle(this.#activeCaptionHandle);
}
}
#findValueByKey = (httpHeaders: Array<IHttpHeader> | undefined, key: string) : string | undefined => {
if (httpHeaders !== undefined) {
for (let i : number = 0; i < httpHeaders.length; ++i) {
if (httpHeaders[i].name === key) {
return httpHeaders[i].value;
}
}
}
return undefined;
}
onLoad = async (urlInfo: IPlayerSessionMediaInfo,
loadParams?: IPlayerSessionLoadParams,
sessionId? : IPlayerSessionId): Promise<void> => {
console.log(`[PlayerService] onLoadが呼び出されました。urlInfo:{}`, JSON.stringify(urlInfo));
if (loadParams !== undefined) {
console.log(`[PlayerService] onLoadのloadParams:{}`, JSON.stringify(loadParams));
}
this.#hasError = false;
const content = {
"uri" : urlInfo.mediaUrl.url,
"container": this.#findValueByKey(urlInfo.mediaUrl.httpHeaders, 'container') || 'FMP4'
};
this.onUnload();
this.#activeSessionId = sessionId;
await this.#initializeVideoPlayer();
this.#msePlayer?.load(content, this.#AUTOPLAY);
}
onUnload = () => {
console.log('[PlayerService] onUnload');
this.#removeEventListeners();
this.#msePlayer?.unload();
this.#msePlayer = undefined;
this.#videoPlayer?.deinitialize();
this.#videoPlayer = undefined;
this.#hasError = false;
// @ts-ignore
global.gmedia = null;
};
handlePlay = () => {
console.log('[PlayerService] handlePlay');
if (this.#videoPlayer) {
this.#videoPlayer.play();
} else {
console.log('[PlayerService] handlePlay this.#videoPlayer is undefined');
}
};
handlePause = () => {
console.log('[PlayerService] handlePause');
if (this.#videoPlayer) {
this.#videoPlayer.pause();
} else {
console.log('[PlayerService] handlePause this.#videoPlayer is undefined');
}
};
handleSeek = (seekTimeSeconds: number) => {
console.log(`[PlayerService] handleSeek with seekTimeSeconds: ${seekTimeSeconds}`);
if (this.#videoPlayer) {
const currentTime = this.#videoPlayer.currentTime;
this.#videoPlayer.currentTime = currentTime + seekTimeSeconds;
} else {
console.log('[PlayerService] handleSeek this.#videoPlayer is undefined');
}
}
handleSetMute = (isMuted: boolean) => {
console.debug('[PlayerService] handleSetMute');
if (this.#videoPlayer) {
this.#videoPlayer.muted = isMuted;
this.updateStatus(this.#activeSessionId);
} else {
console.log('[PlayerService] handleSetMute this.#videoPlayer is undefined');
}
};
handleSetVolume = (volume: number) => {
console.debug('[PlayerService] handleSetVolume');
if (this.#videoPlayer) {
this.#videoPlayer.volume = volume;
this.updateStatus(this.#activeSessionId);
}
}
handleSetActiveTrack = (trackType: string, trackId: string) => {
console.debug(`[PlayerService] handleSetActiveTrack ${trackType} ${trackId}`);
if (trackType === 'AUDIO') {
if (this.#msePlayer) {
const audioTrackList = this.#msePlayer.getAudioLanguages();
if (audioTrackList !== undefined && audioTrackList.length > 0) {
let trackLang: string = audioTrackList[parseInt(trackId)];
this.#msePlayer.selectAudioLanguage(trackLang);
} else {
console.error('[PlayerService] handleSetActiveTrack - audio track list is empty.');
}
} else {
console.error('[PlayerService]: handleSetActiveTrack msePlayer not initialised');
}
} else {
console.debug('[PlayerService] handleSetActiveTrack - only audio track handling is implemented.');
}
}
async start(): Promise<void> { console.log('[PlayerService] start'); this.#playerServerFactory = new PlayerServerFactory(); this.#playerServer = this.#playerServerFactory.getOrMakeServer(); this.#playerServerHandler = new PlayerServerHandler(this); if (this.#playerServerHandler !== undefined) { console.log('[PlayerService] setHandlerを呼び出します'); this.#playerServer?.setHandler( this.#playerServerHandler, this.#serviceComponentId); } await this.#initializeVideoPlayer(); console.log('[PlayerService] サービスが開始されました'); } async stop(): Promise<void> { this.onUnload(); } } const playerService: PlayerService = new PlayerService(); // ライフサイクルメソッド export async function onStartService(): Promise<void> { console.log('[PlayerService] Called onStartService()'); // @amazon-devices/react-native-w3cmediaに必要なglobal.navigatorとgloba.windowを設定します。 // これは、@amazon-devices/react-native-w3cmediaまたはヘッドレスランタイムがアプリ開発者に代わって // 実行するまでの一時的な対応策です。 // @ts-ignore let navigator = global.navigator; if (navigator === undefined) { // @ts-ignore global.navigator = navigator = {}; } /** * React Nativeのグローバル変数を設定します。 * このモジュールを直接使用するか、InitializeCoreを使用できます。 */ // @ts-ignore if (global.window === undefined) { // $FlowExpectedError[cannot-write] globalはほかの場所では書き込み不可ですが、ここでは定義します。 // @ts-ignore global.window = global; } // @ts-ignore if (global.self === undefined) { // $FlowExpectedError[cannot-write] globalはほかの場所では書き込み不可ですが、ここでは定義します。 // @ts-ignore global.self = global; } await playerService.start(); console.log('[PlayerService] サービスが開始されました'); } export async function onStopService(): Promise<void> { console.log('[PlayerService] onStopService()が呼び出されました'); await playerService.stop(); console.log('[PlayerService] サービスが停止されました'); }