Let the music play!  

To try all this out, let's add three sample audio files from a jazz track composed by the exquisite Jesper Buhl Trio called What Is This Thing Called Love. The tracks are already separated by drums, bass, and piano. We can add these .mp3 files to an app/audio folder.

Let's modify our demo composition's tracks in MixerService to provide references to these new real audio files. Open app/modules/mixer/services/mixer.service.ts and make the following modifications:

private _demoComposition(): Array<IComposition> {
// starter composition for user to demo on first launch
return [
{
id: 1,
name: 'Demo',
created: Date.now(),
order: 0,
tracks: [
{
id: 1,
name: 'Drums',
order: 0,
filepath: '~/audio/drums.mp3'
},
{
id: 2,
name: 'Bass',
order: 1,
filepath: '~/audio/bass.mp3'
},
{
id: 3,
name: 'Piano',
order: 2,
filepath: '~/audio/piano.mp3'
}
]
}
];
}

Let's now provide an input to our player controls, which will take our selected composition. Open app/modules/mixer/components/mixer.component.html, and make the following highlighted modification:

<action-bar [title]="composition.name"></action-bar>
<GridLayout rows="*, auto" columns="*" class="page">
<track-list [tracks]="composition.tracks" row="0" col="0">
</track-list>
<player-controls [composition]="composition"
row="1" col="0"></player-controls>
</GridLayout>

Then, in PlayerControlsComponent at app/modules/player/components/player- controls/player-controls.component.ts, we can now observe the state of PlayerService via its various observables:

// angular
import { Component, Input } from '@angular/core';

// libs
import { Subscription } from 'rxjs/Subscription';

// app
import { ITrack, CompositionModel } from '../../../shared/models';
import { PlayerService } from '../../services';

@Component({
moduleId: module.id,
selector: 'player-controls',
templateUrl: 'player- controls.component.html'
})
export class PlayerControlsComponent {

@Input() composition: CompositionModel;

// ui state
public playStatus: string = 'Play';
public duration: number = 0;
public currentTime: number = 0;

// manage subscriptions
private _subPlaying: Subscription;
private _subDuration: Subscription;
private _subCurrentTime: Subscription;

constructor(
private playerService: PlayerService
) { }

public togglePlay() {
this.playerService.togglePlay();
}

ngOnInit() {
// init audio player for composition
this.playerService.composition = this.composition;
// react to play state
this._subPlaying = this.playerService.playing$
.subscribe((playing: boolean) => {
// update button state
this._updateStatus(playing);
// update slider state
if (playing) {
this._subCurrentTime = this.playerService
.currentTime$
.subscribe ((currentTime: number) => {
this.currentTime = currentTime;
});
} else if (this._subCurrentTime) {
this._subCurrentTime.unsubscribe();
}
});
// update duration state for slider
this._subDuration = this.playerService.duration$
.subscribe((duration: number) => {
this.duration = duration;
});
}

ngOnDestroy() {
// cleanup
if (this._subPlaying)
this._subPlaying.unsubscribe();
if (this._subDuration)
this._subDuration.unsubscribe();
if (this._subCurrentTime)
this._subCurrentTime.unsubscribe();
}

private _updateStatus(playing: boolean) {
this.playStatus = playing ? 'Stop' : 'Play';
}
}

The cornerstone of PlayerControlComponent is now its ability to set the active composition via this.playerService.composition = this.composition inside ngOnInit, which is when the composition input is ready, as well as subscribe to the various states provided by PlayerService to update our UI. Most interesting here is the playing$ subscription that manages the currentTime$ subscription based on whether it's playing or not. If you recall, our currentTime$ observable started with Observable.interval(1000), meaning every one second, it will emit the longest track's currentTime, shown here again for reference:

this.currentTime$ = Observable.interval(1000)
.map(_ => this._longestTrack ?
this._longestTrack.player.currentTime
: 0);

We only want to update currentTime of Slider when playback is engaged; hence, the subscription when the playing$ subject emit is true, which will allow our component to receive the player's currentTime every second. When playing$ emit is false, we unsubscribe, to no longer receive the currentTime updates. Excellent.

We also subscribe to our duration$ subject to update the Slider's maxValue. Lastly, we ensure all subscriptions are cleaned up via their Subscription references inside ngOnDestroy.

Let's take a look at our view bindings now for PlayerControlsComponent at app/modules/player/components/player-controls/player-controls.component.html:

<GridLayout rows="100" columns="100,*"
row="1" col="0" class="p-x-10">
<Button [text] ="playStatus" (tap)="togglePlay()"
row="0" col="0" class="btn btn-primary w- 100"></Button>
<Slider [maxValue]="duration" [value]="currentTime"
minValue="0" row="0" col="1" class="slider">
</Slider>
</GridLayout>

If you run the app, you can now select the Demo composition and play music on both iOS and Android.

MUSIC TO OUR EARS! This is pretty awesome. In fact, it's friggin' sweet!!

There are a couple things you may notice or desire at this point:

  • After choosing the Play button, it properly changes to Stop, but when playback reaches the end, it does not return to its original Play text.
  • Slider should also return to position 0 to reset playback.
  • The total duration and currentTime on iOS uses seconds; however, Android uses milliseconds.
  • On iOS, you may notice a very subtle playback sync issue on all the tracks if you choose to play/pause many times during the playback of the composition's demo tracks.
  • The current time and duration labels are needed.
  • Playback seeking would be nice to be able to shuttle our slider to control the position of playback.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.65.1