<commit_before>import {
  IAudioContext,
  IGainNode,
  IMediaElementAudioSourceNode,
} from "standardized-audio-context";
import { SpeakerConfig } from "../types/roundware";
import { ISpeakerPlayer, SpeakerConstructor } from "../types/speaker";
import { cleanAudioURL, speakerLog } from "../utils";

export class SpeakerSyncStreamer implements ISpeakerPlayer {
  isSafeToPlay: boolean = true;
  playing: boolean = false;
  audio: HTMLAudioElement;
  loaded: boolean;
  loadedPercentage: number;
  id: number;
  config: SpeakerConfig;
  uri: string;
  gainNode: IGainNode<IAudioContext>;
  mediaSource: IMediaElementAudioSourceNode<IAudioContext>;
  context: IAudioContext;

  constructor({ audioContext, config, uri, id }: SpeakerConstructor) {
    this.id = id;
    this.config = config;
    this.uri = cleanAudioURL(uri);
    this.loaded = true;
    this.loadedPercentage = 100;
    this.context = audioContext;
    this.audio = new Audio(this.uri);
    this.audio.loop = config.loop || false;
    this.gainNode = audioContext.createGain();
    this.gainNode.gain.value = 0;
    this.audio.crossOrigin = "anonymous";
    this.audio.loop = false;

    this.mediaSource = audioContext.createMediaElementSource(this.audio);
    this.mediaSource.connect(this.gainNode).connect(audioContext.destination);
    this.audio.preload = "auto";

    const that = this;

    this.log(`sync streamer initiaed`);
  }

  started = false;

  async play(): Promise<boolean> {
    try {
      if (this.context.state !== "running") {
        await this.context.resume();
      }

      await this.audio.play();
      this.playing = true;
      // @ts-ignore
      global._roundwareSpeakerStartedAt = new Date();
      this.log(`Playing...`);
    } catch (e) {
      console.error(e);
      this.playing = false;
    }
    return true;
  }
  pause(): void {
    this.audio.pause();
  }
  replay(): void {
    this.audio.currentTime = 0;
  }

  timerStart(): void {
    this.syncTracker = Number(
      setInterval(() => {
        this.trackSync();
      }, this.config.syncCheckInterval || 2500)
    );
    this.play();
  }
  timerStop(): void {
    this.pause();
    clearInterval(this.syncTracker);
  }
  fadingDestination = 0;
  fading = false;
  fade(destinationVolume?: number, duration: number = 3): void {
    if (
      typeof destinationVolume != "number" ||
      (destinationVolume == this.fadingDestination && this.fading)
    )
      return;
    this.fadingDestination = destinationVolume;

    this.fading = true;
    this.gainNode.gain.linearRampToValueAtTime(
      destinationVolume,
      this.context.currentTime + duration
    );
    const that = this;
    setTimeout(() => {
      that.fading = false;
    }, duration * 1000);
  }
  fadeOutAndPause(): void {
    this.fade(0);
  }
  log(string: string): void {
    speakerLog(`[${this.id}] ${string}`);
  }
  onLoadingProgress(callback: (newPercent: number) => void): void {}
  onEnd(callback: () => void): void {}

  updateTime(newTime: number): void {
    this.audio.currentTime = newTime;
  }

  syncTracker?: number;
  trackSync() {
    // @ts-ignore
    const startedAt: Date = global._roundwareSpeakerStartedAt;
    if (!(startedAt instanceof Date)) return;
    if (!this.playing) return;

    const currentTime = new Date().getTime() - startedAt.getTime();
    if (currentTime > this.audio.duration * 1000) return;

    const audioTime = this.audio.currentTime * 1000;

    const difference = currentTime - audioTime;

    this.log(`Difference: ${difference} ms`);
    if (Math.abs(difference) < (this.config.acceptableDelayMs || 50)) {
      // within acceptable range;
      this.audio.playbackRate = 1;
    } else if (Math.abs(difference) > (this.config.syncCheckInterval || 2500)) {
      // difference is too much; try to seek instead; seek bit ahead to compensate buffering time
      this.audio.currentTime = currentTime + 500 / 1000;
      this.audio.playbackRate = 1;
    } else {<commit_msg>ms to s conversion bug<commit_after>import {
  IAudioContext,
  IGainNode,
  IMediaElementAudioSourceNode,
} from "standardized-audio-context";
import { SpeakerConfig } from "../types/roundware";
import { ISpeakerPlayer, SpeakerConstructor } from "../types/speaker";
import { cleanAudioURL, speakerLog } from "../utils";

export class SpeakerSyncStreamer implements ISpeakerPlayer {
  isSafeToPlay: boolean = true;
  playing: boolean = false;
  audio: HTMLAudioElement;
  loaded: boolean;
  loadedPercentage: number;
  id: number;
  config: SpeakerConfig;
  uri: string;
  gainNode: IGainNode<IAudioContext>;
  mediaSource: IMediaElementAudioSourceNode<IAudioContext>;
  context: IAudioContext;

  constructor({ audioContext, config, uri, id }: SpeakerConstructor) {
    this.id = id;
    this.config = config;
    this.uri = cleanAudioURL(uri);
    this.loaded = true;
    this.loadedPercentage = 100;
    this.context = audioContext;
    this.audio = new Audio(this.uri);
    this.audio.loop = config.loop || false;
    this.gainNode = audioContext.createGain();
    this.gainNode.gain.value = 0;
    this.audio.crossOrigin = "anonymous";
    this.audio.loop = false;

    this.mediaSource = audioContext.createMediaElementSource(this.audio);
    this.mediaSource.connect(this.gainNode).connect(audioContext.destination);
    this.audio.preload = "auto";

    const that = this;

    this.log(`sync streamer initiaed`);
  }

  started = false;

  async play(): Promise<boolean> {
    try {
      if (this.context.state !== "running") {
        await this.context.resume();
      }

      await this.audio.play();
      this.playing = true;
      // @ts-ignore
      global._roundwareSpeakerStartedAt = new Date();
      this.log(`Playing...`);
    } catch (e) {
      console.error(e);
      this.playing = false;
    }
    return true;
  }
  pause(): void {
    this.audio.pause();
  }
  replay(): void {
    this.audio.currentTime = 0;
  }

  timerStart(): void {
    this.syncTracker = Number(
      setInterval(() => {
        this.trackSync();
      }, this.config.syncCheckInterval || 2500)
    );
    this.play();
  }
  timerStop(): void {
    this.pause();
    clearInterval(this.syncTracker);
  }
  fadingDestination = 0;
  fading = false;
  fade(destinationVolume?: number, duration: number = 3): void {
    if (
      typeof destinationVolume != "number" ||
      (destinationVolume == this.fadingDestination && this.fading)
    )
      return;
    this.fadingDestination = destinationVolume;

    this.fading = true;
    this.gainNode.gain.linearRampToValueAtTime(
      destinationVolume,
      this.context.currentTime + duration
    );
    const that = this;
    setTimeout(() => {
      that.fading = false;
    }, duration * 1000);
  }
  fadeOutAndPause(): void {
    this.fade(0);
  }
  log(string: string): void {
    speakerLog(`[${this.id}] ${string}`);
  }
  onLoadingProgress(callback: (newPercent: number) => void): void {}
  onEnd(callback: () => void): void {}

  updateTime(newTime: number): void {
    this.audio.currentTime = newTime;
  }

  syncTracker?: number;
  trackSync() {
    // @ts-ignore
    const startedAt: Date = global._roundwareSpeakerStartedAt;
    if (!(startedAt instanceof Date)) return;
    if (!this.playing) return;

    const elapsedTime = new Date().getTime() - startedAt.getTime();
    if (elapsedTime > this.audio.duration * 1000) return;

    const audioTime = this.audio.currentTime * 1000;

    const difference = elapsedTime - audioTime;

    this.log(`Difference: ${difference} ms`);
    if (Math.abs(difference) < (this.config.acceptableDelayMs || 50)) {
      // within acceptable range;
      this.audio.playbackRate = 1;
    } else if (Math.abs(difference) > (this.config.syncCheckInterval || 2500)) {
      // difference is too much; try to seek instead; seek bit ahead to compensate buffering time
      this.audio.currentTime = (elapsedTime + 500) / 1000;
      this.audio.playbackRate = 1;
    } else {
{'fraction_non_alphanumeric': 0.13311483675784697, 'fraction_numerical': 0.009832345896886424, 'mean_word_length': 3.138758476786646, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1668027', 'n_tokens_mistral': 2537, 'n_tokens_neox': 2422, 'n_words': 538}