diff --git a/playground-template/control.mjs b/playground-template/control.mjs
index 3820d2c77..2a932b539 100644
--- a/playground-template/control.mjs
+++ b/playground-template/control.mjs
@@ -224,14 +224,13 @@ export function setupControl(selector, customSettings) {
const settings = new alphaTab.Settings();
applyFonts(settings);
+ settings.fillFromJson(defaultSettings);
settings.fillFromJson({
- ...defaultSettings,
player: {
- ...defaultSettings.player,
scrollElement: viewPort
},
- ...customSettings
});
+ settings.fillFromJson(customSettings);
const at = new alphaTab.AlphaTabApi(el, settings);
at.error.on(function (e) {
diff --git a/playground-template/youtube.css b/playground-template/youtube.css
new file mode 100644
index 000000000..a69ea898d
--- /dev/null
+++ b/playground-template/youtube.css
@@ -0,0 +1,10 @@
+
+.at-wrap {
+ height: calc(90vh - 360px);
+ margin: 0;
+}
+.youtube-wrap {
+ height: 360px;
+ display: flex;
+ justify-content: center;
+}
diff --git a/playground-template/youtube.html b/playground-template/youtube.html
new file mode 100644
index 000000000..4a560c00c
--- /dev/null
+++ b/playground-template/youtube.html
@@ -0,0 +1,144 @@
+
+
+
+
+
+ AlphaTab Control Demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AlphaTabApiBase.ts b/src/AlphaTabApiBase.ts
index dbdbd985a..83fa84ffe 100644
--- a/src/AlphaTabApiBase.ts
+++ b/src/AlphaTabApiBase.ts
@@ -931,9 +931,6 @@ export class AlphaTabApiBase {
return this.renderer.boundsLookup;
}
- // the previously configured player mode to detect changes
- private _playerMode: PlayerMode = PlayerMode.Disabled;
-
/**
* The alphaSynth player used for playback.
* @remarks
@@ -1455,7 +1452,7 @@ export class AlphaTabApiBase {
}
}
- if (mode !== this._playerMode) {
+ if (mode !== this._actualPlayerMode) {
this.destroyPlayer();
}
this.updateCursors();
@@ -1463,7 +1460,6 @@ export class AlphaTabApiBase {
this._actualPlayerMode = mode;
switch (mode) {
case PlayerMode.Disabled:
- this._playerMode = PlayerMode.Disabled;
this.destroyPlayer();
return false;
diff --git a/src/synth/ExternalMediaPlayer.ts b/src/synth/ExternalMediaPlayer.ts
index d59788d01..3a0c08148 100644
--- a/src/synth/ExternalMediaPlayer.ts
+++ b/src/synth/ExternalMediaPlayer.ts
@@ -3,48 +3,120 @@ import type { BackingTrack } from '@src/model/BackingTrack';
import { type IBackingTrackSynthOutput, BackingTrackPlayer } from '@src/synth/BackingTrackPlayer';
import type { ISynthOutputDevice } from '@src/synth/ISynthOutput';
+export interface IExternalMediaHandler {
+ /**
+ * The total duration of the backing track in milliseconds.
+ */
+ readonly backingTrackDuration: number;
+ /**
+ * The playback rate at which the output should playback.
+ */
+ playbackRate: number;
+ /**
+ * The volume at which the output should play (0-1)
+ */
+ masterVolume: number;
+ /**
+ * Instructs the output to seek to the given time position.
+ * @param time The absolute time in milliseconds.
+ */
+ seekTo(time: number): void;
+
+ play(): void;
+ pause(): void;
+}
+
class ExternalMediaSynthOutput implements IBackingTrackSynthOutput {
// fake rate
public readonly sampleRate: number = 44100;
- public backingTrackDuration: number = 0;
- public playbackRate: number = 1;
- public masterVolume: number = 1;
+ private _padding: number = 0;
+ private _seekPosition: number = 0;
+
+ private _handler?: IExternalMediaHandler;
+
+ public get handler(): IExternalMediaHandler | undefined {
+ return this._handler;
+ }
+
+ public set handler(value: IExternalMediaHandler | undefined) {
+ if (value) {
+ if (this._seekPosition !== 0) {
+ value.seekTo(this._seekPosition);
+ this._seekPosition = 0;
+ }
+ }
+
+ this._handler = value;
+ }
+
+ public get backingTrackDuration() {
+ return this.handler?.backingTrackDuration ?? 0;
+ }
+
+ public get playbackRate(): number {
+ return this.handler?.playbackRate ?? 1;
+ }
+
+ public set playbackRate(value: number) {
+ const handler = this.handler;
+ if (handler) {
+ handler.playbackRate = value;
+ }
+ }
+
+ public get masterVolume(): number {
+ return this.handler?.masterVolume ?? 1;
+ }
+
+ public set masterVolume(value: number) {
+ const handler = this.handler;
+ if (handler) {
+ handler.masterVolume = value;
+ }
+ }
- public seekTo(time: number): void {}
+ public seekTo(time: number): void {
+ const handler = this.handler;
+ if (handler) {
+ handler.seekTo(time - this._padding);
+ } else {
+ this._seekPosition = time - this._padding;
+ }
+ }
- public loadBackingTrack(backingTrack: BackingTrack) {}
+ public loadBackingTrack(backingTrack: BackingTrack) {
+ this._padding = backingTrack.padding;
+ }
public open(_bufferTimeInMilliseconds: number): void {
(this.ready as EventEmitter).trigger();
}
public updatePosition(currentTime: number) {
- (this.timeUpdate as EventEmitterOfT).trigger(currentTime);
+ (this.timeUpdate as EventEmitterOfT).trigger(currentTime + this._padding);
}
- public play(): void {}
+ public play(): void {
+ this.handler?.play();
+ }
public destroy(): void {}
- public pause(): void {}
-
- public addSamples(_samples: Float32Array): void {
- // nobody will call this
- }
- public resetSamples(): void {
- // nobody will call this
- }
- public activate(): void {
- // nobody will call this
+ public pause(): void {
+ this.handler?.pause();
}
+ public addSamples(_samples: Float32Array): void {}
+ public resetSamples(): void {}
+ public activate(): void {}
+
public readonly ready: IEventEmitter = new EventEmitter();
public readonly samplesPlayed: IEventEmitterOfT = new EventEmitterOfT();
public readonly timeUpdate: IEventEmitterOfT = new EventEmitterOfT();
public readonly sampleRequest: IEventEmitter = new EventEmitter();
public async enumerateOutputDevices(): Promise {
- const empty:ISynthOutputDevice[] = [];
+ const empty: ISynthOutputDevice[] = [];
return empty;
}
public async setOutputDevice(_device: ISynthOutputDevice | null): Promise {}
@@ -55,6 +127,14 @@ class ExternalMediaSynthOutput implements IBackingTrackSynthOutput {
}
export class ExternalMediaPlayer extends BackingTrackPlayer {
+ public get handler(): IExternalMediaHandler | undefined {
+ return (this.output as ExternalMediaSynthOutput).handler;
+ }
+
+ public set handler(value: IExternalMediaHandler | undefined) {
+ (this.output as ExternalMediaSynthOutput).handler = value;
+ }
+
constructor(bufferTimeInMilliseconds: number) {
super(new ExternalMediaSynthOutput(), bufferTimeInMilliseconds);
}