/ packages / ui / src / services / MediaPlayerService / MediaPlayerService.ts
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  }