chromium/chrome/test/data/webui/cr_components/chromeos/cellular_setup/fake_media_devices.ts

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {assertTrue} from 'chrome://webui-test/chai_assert.js';

export class FakeMediaTrack implements MediaStreamTrack {
  contentHint: string = '';
  enabled: boolean = true;
  id: string = Math.random().toString(36);
  kind: string = 'video';  // Or 'audio' as needed
  label: string = '';
  muted: boolean = false;
  readyState: MediaStreamTrackState = 'live';

  private onendedCallback:
      ((this: MediaStreamTrack, ev: Event) => any)|null = null;
  private onmuteCallback:
      ((this: MediaStreamTrack, ev: Event) => any)|null = null;
  private onunmuteCallback:
      ((this: MediaStreamTrack, ev: Event) => any)|null = null;

  stop(): void {
    this.readyState = 'ended';
    if (this.onendedCallback) {
      this.onendedCallback.call(this, new Event('ended'));
    }
  }

  mute(): void {
    this.muted = true;
    if (this.onmuteCallback) {
      this.onmuteCallback.call(this, new Event('mute'));
    }
  }

  unmute(): void {
    this.muted = false;
    if (this.onunmuteCallback) {
      this.onunmuteCallback.call(this, new Event('unmute'));
    }
  }

  set onended(callback: ((this: MediaStreamTrack, ev: Event) => any)|null) {
    this.onendedCallback = callback;
  }

  set onmute(callback: ((this: MediaStreamTrack, ev: Event) => any)|null) {
    this.onmuteCallback = callback;
  }

  set onunmute(callback: ((this: MediaStreamTrack, ev: Event) => any)|null) {
    this.onunmuteCallback = callback;
  }

  applyConstraints(_constraints?: MediaTrackConstraints): Promise<void> {
    throw new Error('Method not implemented.');
  }

  clone(): MediaStreamTrack {
    throw new Error('Method not implemented.');
  }

  getCapabilities(): MediaTrackCapabilities {
    throw new Error('Method not implemented.');
  }

  getConstraints(): MediaTrackConstraints {
    throw new Error('Method not implemented.');
  }

  getSettings(): MediaTrackSettings {
    throw new Error('Method not implemented.');
  }

  addEventListener<K extends keyof MediaStreamTrackEventMap>(
      _type: K,
      _listener:
          (this: MediaStreamTrack, ev: MediaStreamTrackEventMap[K]) => any,
      _options?: boolean|AddEventListenerOptions): void {}

  removeEventListener<K extends keyof MediaStreamTrackEventMap>(
      _type: K,
      _listener:
          (this: MediaStreamTrack, ev: MediaStreamTrackEventMap[K]) => any,
      _options?: boolean|EventListenerOptions): void {}

  dispatchEvent(_event: Event): boolean {
    throw new Error('Method not implemented.');
  }
}

export class FakeMediaStream implements MediaStream {
  private tracks_: MediaStreamTrack[] = [];

  constructor(tracks: FakeMediaTrack[] = []) {
    this.tracks_ = tracks;
  }
  active: boolean = false;
  id: string = '';

  private onaddtrackCallback:
      ((this: MediaStream, ev: MediaStreamTrackEvent) => any)|null = null;
  private onremovetrackCallback:
      ((this: MediaStream, ev: MediaStreamTrackEvent) => any)|null = null;

  set onaddtrack(callback:
                     ((this: MediaStream, ev: MediaStreamTrackEvent) => any)|
                 null) {
    this.onaddtrackCallback = callback;
  }

  addTrack(track: FakeMediaTrack): void {
    this.tracks_.push(track);
    if (this.onaddtrackCallback) {
      this.onaddtrackCallback.call(
          this, new MediaStreamTrackEvent('addtrack', {track}));
    }
  }

  set onremovetrack(callback:
                        ((this: MediaStream, ev: MediaStreamTrackEvent) => any)|
                    null) {
    this.onremovetrackCallback = callback;
  }

  removeTrack(track: FakeMediaTrack): void {
    const index = this.tracks_.indexOf(track);
    if (index > -1) {
      this.tracks_.splice(index, 1);
      if (this.onremovetrackCallback) {
        this.onremovetrackCallback.call(
            this, new MediaStreamTrackEvent('removetrack', {track}));
      }
    }
  }

  clone(): MediaStream {
    throw new Error('Method not implemented.');
  }

  stop(): void {
    throw new Error('Method not implemented.');
  }

  addEventListener<K extends keyof MediaStreamEventMap>(
      type: K, listener: (this: MediaStream, ev: MediaStreamEventMap[K]) => any,
      options?: boolean|AddEventListenerOptions|undefined): void;
  addEventListener(
      type: string, listener: EventListenerOrEventListenerObject,
      options?: boolean|AddEventListenerOptions|undefined): void;
  addEventListener(_type: unknown, _listener: unknown, _options?: unknown):
      void {
    throw new Error('Method not implemented.');
  }
  removeEventListener<K extends keyof MediaStreamEventMap>(
      type: K, listener: (this: MediaStream, ev: MediaStreamEventMap[K]) => any,
      options?: boolean|EventListenerOptions|undefined): void;
  removeEventListener(
      type: string, listener: EventListenerOrEventListenerObject,
      options?: boolean|EventListenerOptions|undefined): void;
  removeEventListener(_type: unknown, _listener: unknown, _options?: unknown):
      void {
    throw new Error('Method not implemented.');
  }

  dispatchEvent(_event: Event): boolean {
    throw new Error('Method not implemented.');
  }

  getAudioTracks(): MediaStreamTrack[] {
    return this.tracks_.filter(track => track.kind === 'audio');
  }

  getVideoTracks(): MediaStreamTrack[] {
    return this.tracks_.filter(track => track.kind === 'video');
  }

  getTracks(): MediaStreamTrack[] {
    return this.tracks_;
  }

  getTrackById(trackId: string): MediaStreamTrack {
    const track = this.tracks_.find(track => track.id === trackId);
    if (!track) {
      throw new Error('Track not found');
    }
    return track;
  }
}

export class FakeMediaDevices implements MediaDevices {
  isStreamingUserFacingCamera: boolean = true;
  private devices_: MediaDeviceInfo[] = [];
  private deviceChangeListener_: EventListener|null = null;
  private enumerateDevicesResolver_: Function|null = null;
  private getMediaDevicesResolver_: Function|null = null;
  private getMediaDevicesRejectResolver_: Function|null = null;
  private stream_: MediaStream|null = null;
  private shouldUserMediaRequestFail_: boolean = false;

  addEventListener(_type: string, listener: EventListener): void {
    this.deviceChangeListener_ = listener;
  }

  enumerateDevices(): Promise<MediaDeviceInfo[]> {
    return new Promise((res, _rej) => {
      this.enumerateDevicesResolver_ = res;
    });
  }

  resolveEnumerateDevices(callback: Function): void {
    assertTrue(
        !!this.enumerateDevicesResolver_, 'enumerateDevices was not called');
    this.enumerateDevicesResolver_(this.devices_);
    callback();
  }

  getSupportedConstraints(): MediaTrackSupportedConstraints {
    return {
      whiteBalanceMode: false,
      exposureMode: false,
      focusMode: false,
      pointsOfInterest: false,

      exposureCompensation: false,
      colorTemperature: false,
      iso: false,

      brightness: false,
      contrast: false,
      saturation: false,
      sharpness: false,
      focusDistance: false,
      zoom: false,
      torch: false,
    };
  }

  getDisplayMedia(): Promise<MediaStream> {
    return Promise.resolve(new MediaStream());
  }

  getUserMedia(constraints: any): Promise<MediaStream> {
    this.isStreamingUserFacingCamera = constraints.video.facingMode === 'user';
    return new Promise((res, rej) => {
      this.getMediaDevicesResolver_ = res;
      this.getMediaDevicesRejectResolver_ = rej;
    });
  }

  /**
   * Resolves promise returned from getUserMedia().
   */
  resolveGetUserMedia(): void {
    assertTrue(
        !!this.getMediaDevicesResolver_ &&
            !!this.getMediaDevicesRejectResolver_,
        'getUserMedia was not called');

    if (this.shouldUserMediaRequestFail_ && this.stream_) {
      this.getMediaDevicesRejectResolver_!
          ('Failed to create stream, a stream currently exist');
    }

    const track = new FakeMediaTrack();

    track.onended = () => {
      if (this.stream_ && !this.shouldUserMediaRequestFail_ &&
          this.stream_.getTracks().every(t => t.readyState === 'ended')) {
        this.stream_ = null;
      }
    };

    this.stream_ = new FakeMediaStream([track]);
    this.getMediaDevicesResolver_(this.stream_);
  }

  removeEventListener(): void {}

  /**
   * Adds a video input device to the list of media devices.
   */
  addDevice(): void {
    const device = {
      deviceId: '',
      kind: 'videoinput',
      label: '',
      groupId: '',
    } as MediaDeviceInfo;
    // https://w3c.github.io/mediacapture-main/#dom-mediadeviceinfo
    (device as any).__proto__ = MediaDeviceInfo.prototype;
    this.devices_.push(device);
    if (this.deviceChangeListener_) {
      this.deviceChangeListener_(new Event('addDevice'));
    }
  }

  /**
   * Removes the most recently added media device from the list of media
   * devices.
   */
  removeDevice(): void {
    this.devices_.pop();
    if (this.devices_.length <= 1) {
      this.isStreamingUserFacingCamera = true;
    }
    if (this.deviceChangeListener_) {
      this.deviceChangeListener_(new Event('removeDevice'));
    }
  }

  ondevicechange(): void {}

  setShouldUserMediaRequestFail(shouldUserMediaRequestFail: boolean) {
    this.shouldUserMediaRequestFail_ = shouldUserMediaRequestFail;
  }

  dispatchEvent(_event: Event): boolean {
    return false;
  }
}