MediaPlayerService.ts
1 import { Signal } from '@preact/signals-react'; 2 import { inject, injectable } from 'inversify'; 3 import { type IIpfsService, IIpfsServiceSymbol, type ILogService, ILogServiceSymbol, IVideoFile } from 'ipmc-interfaces'; 4 import { IMediaPlayerService } from './IMediaPlayerService'; 5 //@ts-ignore 6 import shaka from 'shaka-player'; 7 8 type Request = shaka.extern.Request; 9 type RequestType = shaka.net.NetworkingEngine.RequestType; 10 type ProgressUpdated = shaka.extern.ProgressUpdated; 11 type HeadersReceived = shaka.extern.HeadersReceived; 12 type SchemePluginConfig = shaka.extern.SchemePluginConfig; 13 14 @injectable() 15 export class MediaPlayerService implements IMediaPlayerService { 16 public constructor( 17 @inject(IIpfsServiceSymbol) private readonly ipfs: IIpfsService, 18 @inject(ILogServiceSymbol) private readonly log: ILogService, 19 ) { 20 this.shakaPlugin = this.shakaPlugin.bind(this); 21 shaka.net.NetworkingEngine.registerScheme('ipfs', this.shakaPlugin, 1, false); 22 } 23 24 public initializeVideo(el: HTMLVideoElement, file: IVideoFile): () => void { 25 this.videoEl = el; 26 const player = new shaka.Player(); 27 this.player = player; 28 player.addEventListener('error', (error: any) => this.log.error(`Error code ${error.code} object ${error}`)); 29 player.configure({ 30 streaming: { 31 rebufferingGoal: 5, 32 bufferingGoal: 30, 33 } 34 }); 35 player.attach(el) 36 .then(() => player.load(`ipfs://${file.cid}/${file.video.name}`)) 37 .then(() => { 38 this.subtitles.value = player.getTextTracks(); 39 this.languages.value = player.getAudioLanguages(); 40 }) 41 .catch((ex: any) => { 42 this.log.error(ex); 43 }); 44 45 return () => { 46 this.languages.value = []; 47 this.subtitles.value = []; 48 this.videoEl = undefined; 49 this.player = undefined; 50 player.unload(); 51 player.destroy(); 52 this.playing.value = false; 53 }; 54 } 55 56 public togglePlay() { 57 if (this.videoEl) { 58 if (this.playing.value) { 59 this.videoEl.pause(); 60 this.playing.value = false; 61 } else { 62 this.videoEl?.play() 63 .then(() => { 64 this.playing.value = true; 65 }); 66 } 67 } 68 } 69 70 public selectSubtitle(trackName?: string) { 71 if (trackName) { 72 this.player.value.selectTextTrack(trackName); 73 this.player.value.setTextTrackVisibility(true); 74 } else { 75 this.player.value.setTextTrackVisibility(false); 76 } 77 78 } 79 80 public languages = new Signal([]); 81 82 public subtitles = new Signal([]); 83 84 public playing = new Signal(false); 85 86 private async shakaPlugin( 87 uri: string, 88 request: Request, 89 requestType: RequestType, 90 progressUpdated: ProgressUpdated, 91 headersReceived: HeadersReceived, 92 config: SchemePluginConfig 93 ) { 94 const fullPath = uri.substring(uri.indexOf('://') + 3); 95 const paths = fullPath.split('/'); 96 const cid = paths.shift()!; 97 const path = paths.join('/'); 98 99 headersReceived({}); 100 101 const data = await this.ipfs.fetch(cid, path); 102 103 return { 104 uri: uri, 105 originalUri: uri, 106 data: data, 107 status: 200, 108 }; 109 }; 110 111 private videoEl: HTMLVideoElement | undefined; 112 private player: shaka.Player | undefined; 113 }