Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add missing Sound features .seek(), .duration, & getTotalPlaybackDuration() #2340

Merged
merged 7 commits into from
Jun 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,38 @@ This project adheres to [Semantic Versioning](http://semver.org/).
-

### Added

- Allowed setting playback `ex.Sound.duration` which will limit the amount of time that a clip plays from the current playback position.
- Added a new lightweight `ex.StateMachine` type for building finite state machines
```typescript
const machine = ex.StateMachine.create({
start: 'STOPPED',
states: {
PLAYING: {
onEnter: () => {
console.log("playing");
},
transitions: ['STOPPED', 'PAUSED']
},
STOPPED: {
onEnter: () => {
console.log("stopped");
},
transitions: ['PLAYING', 'SEEK']
},
SEEK: {
transitions: ['*']
},
PAUSED: {
onEnter: () => {
console.log("paused")
},
transitions: ['PLAYING', 'STOPPED']
}
}
});
```
- Added `ex.Sound.seek(positionInSeconds)` which will allow you to see to a place in the sound, this will implicitly pause the sound
- Added `ex.Sound.getTotalPlaybackDuration()` which will return the total time in the sound in seconds.
- Allow tinting of `ex.Sprite`'s by setting a new `tint` property, renderers must support the tint property in order to function.
```typescript
const imageSource = new ex.ImageSource('./path/to/image.png');
Expand All @@ -39,7 +70,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Fixed

-
- Fixed issue where `ex.Sound` wasn't being paused when the browser window lost focus

### Updates

Expand Down
34 changes: 30 additions & 4 deletions sandbox/tests/sound/sound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,27 @@ var loader = new ex.Loader([
sound
]);

var label = new ex.Label({
var currentTimeLabel = new ex.Label({
x: 50,
y: 100,
text: 'Current Time: 0'
});
label.font = new ex.Font({
currentTimeLabel.font = new ex.Font({
size: 30,
family: 'sans-serif'
});
game.currentScene.add(label);
game.currentScene.add(currentTimeLabel);

var totalTimeLabel = new ex.Label({
x: 350,
y: 100,
text: 'Total Time: 0'
});
totalTimeLabel.font = new ex.Font({
size: 30,
family: 'sans-serif'
});
game.currentScene.add(totalTimeLabel);

var play = new ex.Actor({
x: 200,
Expand All @@ -45,9 +56,24 @@ pause.on('pointerdown', () => {
});
game.currentScene.add(pause);

var pause = new ex.Actor({
x: 400,
y: 200,
width: 40,
height: 40,
color: ex.Color.Yellow
});
pause.on('pointerdown', () => {
sound.duration = 2;
sound.seek(5);
sound.play();
});
game.currentScene.add(pause);


game.currentScene.onPostUpdate = () => {
label.text = 'Current Time: ' + sound.getPlaybackPosition();
currentTimeLabel.text = 'Current Time: ' + sound.getPlaybackPosition().toFixed(2);
totalTimeLabel.text = 'Total Time: ' + sound.getTotalPlaybackDuration().toFixed(2);
}

game.start(loader);
16 changes: 16 additions & 0 deletions src/engine/Interfaces/Audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,27 @@ export interface Audio {
*/
isPlaying(): boolean;

/**
* Returns if the audio is paused
*/
isPaused(): boolean;

/**
* Will play the sound or resume if paused
*/
play(): Promise<any>;

/**
* Seek to a position (in seconds) in the audio
* @param position
*/
seek(position: number): void;

/**
* Return the duration of the sound
*/
getTotalPlaybackDuration(): number;

/**
* Return the current playback time of the playing track in seconds from the start
*/
Expand Down
8 changes: 8 additions & 0 deletions src/engine/Loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Vector } from './Math/vector';
import { delay } from './Util/Util';
import { ImageFiltering } from './Graphics/Filtering';
import { clamp } from './Math/util';
import { Sound } from './Resources/Sound/Sound';

/**
* Pre-loading assets
Expand Down Expand Up @@ -333,6 +334,13 @@ export class Loader extends Class implements Loadable<Loadable<any>[]> {
})
)
);
// Wire all sound to the engine
for (const resource of this._resourceList) {
if (resource instanceof Sound) {
resource.wireEngine(this._engine);
}
}

this._isLoadedResolve();

// short delay in showing the button for aesthetics
Expand Down
50 changes: 39 additions & 11 deletions src/engine/Resources/Sound/Sound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,23 @@ export class Sound extends Class implements Audio, Loadable<AudioBuffer> {
return this._volume;
}

private _duration: number | undefined;
/**
* Get the duration that this audio should play. If unset the total natural playback duration will be used.
*/
public get duration(): number | undefined {
return this._duration;
}
/**
* Set the duration that this audio should play. If unset the total natural playback duration will be used.
*
* Note: if you seek to a specific point the duration will start from that point, for example
*
* If you have a 10 second clip, seek to 5 seconds, then set the duration to 2, it will play the clip from 5-7 seconds.
*/
public set duration(duration: number | undefined){
this._duration = duration;
}

/**
* Return array of Current AudioInstances playing or being paused
Expand All @@ -72,9 +86,8 @@ export class Sound extends Class implements Audio, Loadable<AudioBuffer> {

private _loop = false;
private _volume = 1;
private _duration: number | undefined = undefined;
private _isStopped = false;
private _isPaused = false;
// private _isPaused = false;
private _tracks: Audio[] = [];
private _engine: Engine;
private _wasPlayingOnHidden: boolean = false;
Expand Down Expand Up @@ -117,7 +130,7 @@ export class Sound extends Class implements Audio, Loadable<AudioBuffer> {
}
const arraybuffer = await this._resource.load();
const audiobuffer = await this.decodeAudio(arraybuffer.slice(0));
this._duration = typeof audiobuffer === 'object' ? audiobuffer.duration : undefined;
this._duration = this._duration ?? audiobuffer?.duration ?? undefined;
this.emit('processed', new NativeSoundProcessedEvent(this, audiobuffer));
return this.data = audiobuffer;
}
Expand Down Expand Up @@ -178,6 +191,10 @@ export class Sound extends Class implements Audio, Loadable<AudioBuffer> {
return this._tracks.some((t) => t.isPlaying());
}

public isPaused(): boolean {
return this._tracks.some(t => t.isPaused());
}

/**
* Play the sound, returns a promise that resolves when the sound is done playing
* An optional volume argument can be passed in to play the sound. Max volume is 1.0
Expand All @@ -196,7 +213,7 @@ export class Sound extends Class implements Audio, Loadable<AudioBuffer> {

this.volume = volume || this.volume;

if (this._isPaused) {
if (this.isPaused()) {
return this._resumePlayback();
} else {
return this._startPlayback();
Expand All @@ -215,8 +232,6 @@ export class Sound extends Class implements Audio, Loadable<AudioBuffer> {
track.pause();
}

this._isPaused = true;

this.emit('pause', new NativeSoundEvent(this));

this.logger.debug('Paused all instances of sound', this.path);
Expand All @@ -232,7 +247,6 @@ export class Sound extends Class implements Audio, Loadable<AudioBuffer> {

this.emit('stop', new NativeSoundEvent(this));

this._isPaused = false;
this._tracks.length = 0;
this.logger.debug('Stopped all instances of sound', this.path);
}
Expand All @@ -248,6 +262,18 @@ export class Sound extends Class implements Audio, Loadable<AudioBuffer> {
});
}

public seek(position: number, trackId = 0) {
if (this._tracks.length === 0) {
this._getTrackInstance(this.data);
}

this._tracks[trackId].seek(position);
}

public getTotalPlaybackDuration() {
return this.data.duration;
}

/**
* Return the current playback time of the playing track in seconds from the start.
*
Expand All @@ -272,15 +298,17 @@ export class Sound extends Class implements Audio, Loadable<AudioBuffer> {
}

private async _resumePlayback(): Promise<boolean> {
if (this._isPaused) {
if (this.isPaused) {
const resumed: Promise<boolean>[] = [];
// ensure we resume *current* tracks (if paused)
for (const track of this._tracks) {
resumed.push(track.play());
resumed.push(track.play().then(() => {
this.emit('playbackend', new NativeSoundEvent(this, track as WebAudioInstance));
this._tracks.splice(this.getTrackId(track), 1);
return true;
}));
}

this._isPaused = false;

this.emit('resume', new NativeSoundEvent(this));

this.logger.debug('Resuming paused instances for sound', this.path, this._tracks);
Expand Down
Loading