import { SelfieSegmentation , SelfieSegmentationConfig , Results as SelfieSegmentationResults , } from "@mediapipe/selfie_segmentation" ; /**
 *  VirtualBackgroundProcessor . startProcessing メソッドに指定可能なオプション
  interface VirtualBackgroundProcessorOptions { /**
     * 仮想背景に使用する画像
     *
     * 省略された場合には、元々の背景が使用されます
     */ backgroundImage ? : CanvasImageSourceWebCodecs ; /**
     * 背景ぼかし効果の半径（pixel）
     *
     * 値が大きいほど、ぼかしが強くなります。
     * デフォルトではぼかしは無効で、これは値として0を指定した場合の挙動と等しいです。
     */ blurRadius ? : number ; /**
     * セグメンテーションに使用するモデル
     *
     * デフォルトでは"selfie-landscape"が使用されます。
     * それぞれのモデルの詳細については
     * [MediaPipe Selfie Segmentation](https://google.github.io/mediapipe/solutions/selfie_segmentation.html)
     * を参照してください。
     */ segmentationModel ? : "selfie-landscape" | "selfie-general" ; } /**
 * 映像トラックに仮想背景処理を適用するためのプロセッサ
 */ class VirtualBackgroundProcessor { private segmentation : SelfieSegmentation ; private trackProcessor ? : TrackProcessor ; /**
     *  VirtualBackgroundProcessor インスタンスを生成します
     *
     *  param assetsPath constructor ( assetsPath ) { const config = { } ; assetsPath = trimLastSlash ( assetsPath ) ; config . locateFile = ( file ) => { return `${ assetsPath }/${ file }` ; } ; this . segmentation = new SelfieSegmentation ( config ) ; } returns static isSupported ( ) { return ! ( typeof MediaStreamTrackProcessor === "undefined" || typeof MediaStreamTrackGenerator === "undefined" ) ; } param track param options returns throws 既にあるトラックを処理中の場合には、エラーが送出されます
     *
     * 処理中かどうかは  VirtualBackgroundProcessor . isProcessing で判定可能です
      startProcessing ( track , options = { } ) { if ( this . isProcessing ( ) ) { throw Error ( "Virtual background processing has already started." ) ; } this . trackProcessor = new TrackProcessor ( track , this . segmentation , options ) ; return this . trackProcessor . startProcessing ( ) ; } /**
     * 仮想背景処理の適用を停止します
     *
     * コンストラクタに渡された映像トラックは閉じないので、
     * 必要であれば、別途呼び出し側で対処する必要があります
     */ stopProcessing ( ) { if ( this . trackProcessor !== undefined ) { this . trackProcessor . stopProcessing ( ) ; this . trackProcessor = undefined ; } } returns isProcessing ( ) { return this . trackProcessor !== undefined ; } } class TrackProcessor { private options : VirtualBackgroundProcessorOptions ; private track : MediaStreamVideoTrack ; private canvas : OffscreenCanvas ; private canvasCtx : OffscreenCanvasRenderingContext2D ; private segmentation : SelfieSegmentation ; private abortController : AbortController ; constructor ( track , segmentation , options ) { this . options = options ; this . track = track ; const width = track . getSettings ( ) . width ; const height = track . getSettings ( ) . height ; if ( width === undefined || height === undefined ) { throw Error ( `Could not retrieve the resolution of the video track: {track}` ) ; } this . canvas = new OffscreenCanvas ( width , height ) ; const canvasCtx = this . canvas . getContext ( "2d" , { desynchronized : true } ) ; if ( ! canvasCtx ) { throw Error ( "Failed to get the 2D context of an OffscreenCanvas" ) ; } this . canvasCtx = canvasCtx ; let modelSelection = 1 ; if ( options . segmentationModel && options . segmentationModel === "selfie-general" ) { modelSelection = 0 ; } segmentation . setOptions ( { modelSelection , } ) ; segmentation . onResults ( ( results ) => { this . updateOffscreenCanvas ( results ) ; } ) ; this . segmentation = segmentation ; this . abortController = new AbortController ( ) ; } async startProcessing ( ) { await this . segmentation . initialize ( ) ; const signal = this . abortController . signal ; const generator = new MediaStreamTrackGenerator ( { kind : "video" } ) ; const processor = new MediaStreamTrackProcessor ( { track : this . track } ) ; processor . readable . pipeThrough ( new TransformStream ( { transform : async ( frame , controller ) => { await this . transform ( frame , controller ) ; } , } ) , { signal } ) . pipeTo ( generator . writable ) . catch ( ( e ) => { if ( signal . aborted ) { console . debug ( "Shutting down streams after abort." ) ; } else { console . warn ( "Error from stream transform:" , e ) ; } processor . readable . cancel ( e ) . catch ( ( e ) => { console . warn ( "Failed to cancel `MediaStreamTrackProcessor`:" , e ) ; } ) ; generator . writable . abort ( e ) . catch ( ( e ) => { console . warn ( "Failed to abort `MediaStreamTrackGenerator`:" , e ) ; } ) ; } ) ; return generator ; } stopProcessing ( ) { this . abortController . abort ( ) ; this . segmentation . onResults ( ( ) => { } ) ; } private async transform ( frame , controller ) { const image = await createImageBitmap ( frame ) ; await this . segmentation . send ( { image } ) ; image . close ( ) ; frame . close ( ) ; controller . enqueue ( new VideoFrame ( this . canvas ) ) ; } private updateOffscreenCanvas ( segmentationResults ) { this . canvasCtx . save ( ) ; this . canvasCtx . clearRect ( 0 , 0 , this . canvas . width , this . canvas . height ) ; this . canvasCtx . drawImage ( segmentationResults . segmentationMask , 0 , 0 , this . canvas . width , this . canvas . height ) ; this . canvasCtx . globalCompositeOperation = "source-in" ; this . canvasCtx . drawImage ( segmentationResults . image , 0 , 0 , this . canvas . width , this . canvas . height ) ; if ( this . options . blurRadius !== undefined ) { this . canvasCtx . filter = `blur(${ this . options . blurRadius }px)` ; } this . canvasCtx . globalCompositeOperation = "destination-over" ; if ( this . options . backgroundImage !== undefined ) { this . canvasCtx . drawImage ( this . options . backgroundImage , 0 , 0 , this . canvas . width , this . canvas . height ) ; } else { this . canvasCtx . drawImage ( segmentationResults . image , 0 , 0 ) ; } this . canvasCtx . restore ( ) ; } } function trimLastSlash ( s ) { if ( s . slice ( - 1 ) === "/" ) { return s . slice ( 0 , - 1 ) ; } return s ; } export { VirtualBackgroundProcessorOptions , VirtualBackgroundProcessor } ;