diff --git a/dist/ionic-audio-cordova-track.ts b/dist/ionic-audio-cordova-track.ts old mode 100644 new mode 100755 index 9365f31..ad4d2aa --- a/dist/ionic-audio-cordova-track.ts +++ b/dist/ionic-audio-cordova-track.ts @@ -1,245 +1,245 @@ -import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; -import {Injectable, Inject, Optional} from '@angular/core'; - -declare let Media: any; - -/** - * Cordova Media audio track - * - * @export - * @class CordovaAudioTrack - * @constructor - * @implements {IAudioTrack} - */ -@Injectable() -export class CordovaAudioTrack implements IAudioTrack { - private audio: any; - public isPlaying: boolean = false; - public isFinished: boolean = false; - private _progress: number = 0; - private _completed: number = 0; - private _duration: number; - private _id: number; - private _isLoading: boolean; - private _hasLoaded: boolean; - private _timer: any; - - constructor(public src: string) { - if (window['cordova'] === undefined || window['Media'] === undefined) { - console.log('Cordova Media is not available'); - return; - }; - - this.createAudio(); - } - - private createAudio() { - this.audio = new Media(this.src, () => { - console.log('Finished playback'); - this.stopTimer(); - this.isFinished = true; - this.destroy(); // TODO add parameter to control whether to release audio on stop or finished - }, (err) => { - console.log(`Audio error => track ${this.src}`, err); - }, (status) => { - switch (status) { - case Media.MEDIA_STARTING: - console.log(`Loaded track ${this.src}`); - this._hasLoaded = true; - break; - case Media.MEDIA_RUNNING: - console.log(`Playing track ${this.src}`); - this.isPlaying = true; - this._isLoading = false; - break; - case Media.MEDIA_PAUSED: - this.isPlaying = false; - break - case Media.MEDIA_STOPPED: - this.isPlaying = false; - break; - } - }); - } - - private startTimer() { - this._timer = setInterval(() => { - if (this._duration===undefined || this._duration < 0) { - this._duration = Math.round(this.audio.getDuration()*100)/100; - } - - this.audio.getCurrentPosition((position) => { - if (position > -1) { - this._progress = Math.round(position*100)/100; - this._completed = this._duration > 0 ? Math.round(this._progress / this._duration * 100)/100 : 0; - } - }, (e) => { - console.log("Error getting position", e); - } - ); - }, 1000); - } - - private stopTimer() { - clearInterval(this._timer); - } - - /** public members */ - - /** - * Gets the track id - * - * @property id - * @type {number} - */ - public get id() : number { - return this._id; - } - - /** - * Sets the track id - * - * @property id - */ - public set id(v : number) { - this._id = v; - } - - /** - * Gets the track duration, or -1 if it cannot be determined - * - * @property duration - * @readonly - * @type {number} - */ - public get duration() : number { - return this._duration; - } - - /** - * Gets current track time (progress) - * - * @property progress - * @readonly - * @type {number} - */ - public get progress() : number { - return this._progress; - } - - /** - * Gets current track progress as a percentage - * - * @property completed - * @readonly - * @type {number} - */ - public get completed() : number { - return this._completed; - } - -/** - * Gets any errors logged by HTML5 audio - * - * @property error - * @readonly - * @type {MediaError} - */ - public get error() : MediaError { - return this.audio.error; - } - - /** - * Gets a boolean value indicating whether the current source can be played - * - * @property canPlay - * @readonly - * @type {boolean} - */ - public get canPlay() : boolean { - return true; - } - - /** - * Gets a boolean value indicating whether the track is in loading state - * - * @property isLoading - * @readonly - * @type {boolean} - */ - public get isLoading() : boolean { - return this._isLoading; - } - - /** - * Gets a boolean value indicating whether the track has finished loading - * - * @property hadLoaded - * @readonly - * @type {boolean} - */ - public get hasLoaded() : boolean { - return this._hasLoaded; - } - - /** - * Plays current track - * - * @method play - */ - play() { - if (!this.audio) { - this.createAudio(); - } - - if (!this._hasLoaded) { - console.log(`Loading track ${this.src}`); - this._isLoading = true; - } - - this.audio.play(); - this.startTimer(); - } - - /** - * Pauses current track - * - * @method pause - */ - pause() { - if (!this.isPlaying) return; - console.log(`Pausing track ${this.src}`); - this.audio.pause(); - this.stopTimer(); - } - - /** - * Stops current track and releases audio - * - * @method stop - */ - stop() { - this.audio.stop(); // calls Media onSuccess callback - } - - /** - * Seeks to a new position within the track - * - * @method seekTo - * @param {number} time the new position (milliseconds) to seek to - */ - seekTo(time: number) { - // Cordova Media reports duration and progress as seconds, so we need to multiply by 1000 - this.audio.seekTo(time*1000); - } - - /** - * Releases audio resources - * - * @method destroy - */ - destroy() { - this.audio.release(); - console.log(`Released track ${this.src}`); - } +import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; +import {Injectable, Inject, Optional} from '@angular/core'; + +declare let Media: any; + +/** + * Cordova Media audio track + * + * @export + * @class CordovaAudioTrack + * @constructor + * @implements {IAudioTrack} + */ +@Injectable() +export class CordovaAudioTrack implements IAudioTrack { + private audio: any; + public isPlaying: boolean = false; + public isFinished: boolean = false; + private _progress: number = 0; + private _completed: number = 0; + private _duration: number; + private _id: number; + private _isLoading: boolean; + private _hasLoaded: boolean; + private _timer: any; + + constructor(public src: string) { + if (window['cordova'] === undefined || window['Media'] === undefined) { + console.log('Cordova Media is not available'); + return; + }; + + this.createAudio(); + } + + private createAudio() { + this.audio = new Media(this.src, () => { + console.log('Finished playback'); + this.stopTimer(); + this.isFinished = true; + this.destroy(); // TODO add parameter to control whether to release audio on stop or finished + }, (err) => { + console.log(`Audio error => track ${this.src}`, err); + }, (status) => { + switch (status) { + case Media.MEDIA_STARTING: + console.log(`Loaded track ${this.src}`); + this._hasLoaded = true; + break; + case Media.MEDIA_RUNNING: + console.log(`Playing track ${this.src}`); + this.isPlaying = true; + this._isLoading = false; + break; + case Media.MEDIA_PAUSED: + this.isPlaying = false; + break + case Media.MEDIA_STOPPED: + this.isPlaying = false; + break; + } + }); + } + + private startTimer() { + this._timer = setInterval(() => { + if (this._duration===undefined || this._duration < 0) { + this._duration = Math.round(this.audio.getDuration()*100)/100; + } + + this.audio.getCurrentPosition((position) => { + if (position > -1) { + this._progress = Math.round(position*100)/100; + this._completed = this._duration > 0 ? Math.round(this._progress / this._duration * 100)/100 : 0; + } + }, (e) => { + console.log("Error getting position", e); + } + ); + }, 1000); + } + + private stopTimer() { + clearInterval(this._timer); + } + + /** public members */ + + /** + * Gets the track id + * + * @property id + * @type {number} + */ + public get id() : number { + return this._id; + } + + /** + * Sets the track id + * + * @property id + */ + public set id(v : number) { + this._id = v; + } + + /** + * Gets the track duration, or -1 if it cannot be determined + * + * @property duration + * @readonly + * @type {number} + */ + public get duration() : number { + return this._duration; + } + + /** + * Gets current track time (progress) + * + * @property progress + * @readonly + * @type {number} + */ + public get progress() : number { + return this._progress; + } + + /** + * Gets current track progress as a percentage + * + * @property completed + * @readonly + * @type {number} + */ + public get completed() : number { + return this._completed; + } + +/** + * Gets any errors logged by HTML5 audio + * + * @property error + * @readonly + * @type {MediaError} + */ + public get error() : MediaError { + return this.audio.error; + } + + /** + * Gets a boolean value indicating whether the current source can be played + * + * @property canPlay + * @readonly + * @type {boolean} + */ + public get canPlay() : boolean { + return true; + } + + /** + * Gets a boolean value indicating whether the track is in loading state + * + * @property isLoading + * @readonly + * @type {boolean} + */ + public get isLoading() : boolean { + return this._isLoading; + } + + /** + * Gets a boolean value indicating whether the track has finished loading + * + * @property hadLoaded + * @readonly + * @type {boolean} + */ + public get hasLoaded() : boolean { + return this._hasLoaded; + } + + /** + * Plays current track + * + * @method play + */ + play() { + if (!this.audio) { + this.createAudio(); + } + + if (!this._hasLoaded) { + console.log(`Loading track ${this.src}`); + this._isLoading = true; + } + + this.audio.play(); + this.startTimer(); + } + + /** + * Pauses current track + * + * @method pause + */ + pause() { + if (!this.isPlaying) return; + console.log(`Pausing track ${this.src}`); + this.audio.pause(); + this.stopTimer(); + } + + /** + * Stops current track and releases audio + * + * @method stop + */ + stop() { + this.audio.stop(); // calls Media onSuccess callback + } + + /** + * Seeks to a new position within the track + * + * @method seekTo + * @param {number} time the new position (milliseconds) to seek to + */ + seekTo(time: number) { + // Cordova Media reports duration and progress as seconds, so we need to multiply by 1000 + this.audio.seekTo(time*1000); + } + + /** + * Releases audio resources + * + * @method destroy + */ + destroy() { + this.audio.release(); + console.log(`Released track ${this.src}`); + } } \ No newline at end of file diff --git a/dist/ionic-audio-interfaces.ts b/dist/ionic-audio-interfaces.ts old mode 100644 new mode 100755 index 2077251..9fbbe5c --- a/dist/ionic-audio-interfaces.ts +++ b/dist/ionic-audio-interfaces.ts @@ -1,60 +1,61 @@ - -/** - * Defines the audio provider contract - * - * @export - * @interface IAudioProvider - */ -export interface IAudioProvider { - current: number; - tracks: IAudioTrack[]; - - create(track: ITrackConstraint): IAudioTrack; - add(track: IAudioTrack); - play(index: number); - pause(index?: number); - stop(index?: number); -} - -/** - * Defines the properties for JSON objects representing tracks to be played - * - * @export - * @interface ITrackConstraint - */ -export interface ITrackConstraint { - id?:number; - src: string; - title?: string; - artist?: string; - art?: string; - preload?: string; -} - -/** - * Defines the audio track contract - * - * @export - * @interface IAudioTrack - * @extends {ITrackConstraint} - */ -export interface IAudioTrack extends ITrackConstraint { - src: string; - id: number; - isPlaying: boolean; - isLoading: boolean; - isFinished: boolean; - duration: number; - progress: number; - completed: number; - canPlay: boolean; - error: MediaError; - - play(); - pause(); - stop(); - seekTo(time: number); - destroy(); -} - - + +/** + * Defines the audio provider contract + * + * @export + * @interface IAudioProvider + */ +export interface IAudioProvider { + current: number; + tracks: IAudioTrack[]; + + create(track: ITrackConstraint): IAudioTrack; + add(track: IAudioTrack); + play(index: number); + pause(index?: number); + stop(index?: number); + seekTo(time: number); +} + +/** + * Defines the properties for JSON objects representing tracks to be played + * + * @export + * @interface ITrackConstraint + */ +export interface ITrackConstraint { + id?:number; + src: string; + title?: string; + artist?: string; + art?: string; + preload?: string; +} + +/** + * Defines the audio track contract + * + * @export + * @interface IAudioTrack + * @extends {ITrackConstraint} + */ +export interface IAudioTrack extends ITrackConstraint { + src: string; + id: number; + isPlaying: boolean; + isLoading: boolean; + isFinished: boolean; + duration: number; + progress: number; + completed: number; + canPlay: boolean; + error: MediaError; + + play(); + pause(); + stop(); + seekTo(time: number); + destroy(); +} + + diff --git a/dist/ionic-audio-providers.ts b/dist/ionic-audio-providers.ts old mode 100644 new mode 100755 index 9989e2f..3dea2f5 --- a/dist/ionic-audio-providers.ts +++ b/dist/ionic-audio-providers.ts @@ -1,178 +1,180 @@ -import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; -import {Injectable, Inject, Optional} from '@angular/core'; -import {WebAudioTrack} from './ionic-audio-web-track'; -import {CordovaAudioTrack} from './ionic-audio-cordova-track'; - - -/** - * Base class for audio providers - * - * @export - * @abstract - * @class AudioProvider - * @implements {IAudioProvider} - */ -@Injectable() -export abstract class AudioProvider implements IAudioProvider { - protected static tracks: IAudioTrack[] = []; - protected _current: number; - - /** - * Creates an audio provider based on the environment. - * If running from within a browser, then defaults to HTML5 Audio. If running on a device, it will check for Cordova and Media plugins and use - * a native audio player, otherwise falls back to HTML5 audio. - * - * @method factory - * @static - * @return {IAudioProvider} An IAudioProvider instance - */ - static factory() { - return window.hasOwnProperty('cordova') && window.hasOwnProperty('Media') ? new CordovaMediaProvider() : new WebAudioProvider(); - } - - constructor() { - } - - /** - * Creates an IAudioTrack instance from a JSON object. - * Not implemented in base class. - * - * @method create - * @param {ITrackConstraint} track A JSON object containing at least a src property - * @return null - */ - create(track: ITrackConstraint) { - console.error('Not implemented in base class'); - return null; - } - - /** - * Adds an existing IAudioTrack instance to the array of managed tracks. - * - * @method add - * @param {IAudioTrack} audioTrack An instance of IAudioTrack - */ - add(audioTrack: IAudioTrack) { - AudioProvider.tracks.push(audioTrack); - }; - - /** - * Plays a given track. - * - * @method play - * @param {number} index The track id - */ - play(index: number) { - if (index===undefined || index > AudioProvider.tracks.length-1) return; - this._current = index; - AudioProvider.tracks[index].play(); - }; - - /** - * Pauses a given track. - * - * @method pause - * @param {number} [index] The track id, or if undefined it will pause whichever track currently playing - */ - pause(index?: number) { - if (this._current===undefined || index > AudioProvider.tracks.length-1) return; - index = index || this._current; - AudioProvider.tracks[index].pause(); - }; - - /** - * Stops a given track. - * - * @method stop - * @param {number} [index] The track id, or if undefined it will stop whichever track currently playing - */ - stop(index?: number) { - if (this._current===undefined || index > AudioProvider.tracks.length-1) return; - index = index || this._current; - AudioProvider.tracks[index].stop(); - this._current = undefined; - }; - - /** - * Gets an array of tracks managed by this provider - * - * @property tracks - * @readonly - * @type {IAudioTrack[]} - */ - public get tracks() : IAudioTrack[] { - return AudioProvider.tracks; - } - - /** - * Gets current track id - * - * @property current - * @type {number} - */ - public get current() : number { - return this._current; - } - - /** - * Sets current track id - * - * @property current - */ - public set current(v : number) { - this._current = v; - } - -} - -/** - * Creates an HTML5 audio provider - * - * @export - * @class WebAudioProvider - * @constructor - * @extends {AudioProvider} - */ -@Injectable() -export class WebAudioProvider extends AudioProvider { - - constructor() { - super(); - console.log('Using Web Audio provider'); - } - - create(track: ITrackConstraint) { - let audioTrack = new WebAudioTrack(track.src, track.preload); - Object.assign(audioTrack, track); - let trackId = WebAudioProvider.tracks.push(audioTrack); - audioTrack.id = trackId-1; - return audioTrack; - } - -} - -/** - * Creates a Cordova audio provider - * - * @export - * @class CordovaMediaProvider - * @constructor - * @extends {AudioProvider} - */ -@Injectable() -export class CordovaMediaProvider extends AudioProvider { - - constructor() { - super(); - console.log('Using Cordova Media provider'); - } - - create(track: ITrackConstraint) { - let audioTrack = new CordovaAudioTrack(track.src); - Object.assign(audioTrack, track); - let trackId = CordovaMediaProvider.tracks.push(audioTrack); - audioTrack.id = trackId-1; - return audioTrack; - } - +import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; +import {Injectable, Inject, Optional} from '@angular/core'; +import {WebAudioTrack} from './ionic-audio-web-track'; +import {CordovaAudioTrack} from './ionic-audio-cordova-track'; + + +/** + * Base class for audio providers + * + * @export + * @abstract + * @class AudioProvider + * @implements {IAudioProvider} + */ +@Injectable() +export abstract class AudioProvider implements IAudioProvider { + protected static tracks: IAudioTrack[] = []; + protected _current: number; + + /** + * Creates an audio provider based on the environment. + * If running from within a browser, then defaults to HTML5 Audio. If running on a device, it will check for Cordova and Media plugins and use + * a native audio player, otherwise falls back to HTML5 audio. + * + * @method factory + * @static + * @return {IAudioProvider} An IAudioProvider instance + */ + static factory() { + return window.hasOwnProperty('cordova') && window.hasOwnProperty('Media') ? new CordovaMediaProvider() : new WebAudioProvider(); + } + + constructor() { + } + + /** + * Creates an IAudioTrack instance from a JSON object. + * Not implemented in base class. + * + * @method create + * @param {ITrackConstraint} track A JSON object containing at least a src property + * @return null + */ + create(track: ITrackConstraint) { + console.error('Not implemented in base class'); + return null; + } + + /** + * Adds an existing IAudioTrack instance to the array of managed tracks. + * + * @method add + * @param {IAudioTrack} audioTrack An instance of IAudioTrack + */ + add(audioTrack: IAudioTrack) { + AudioProvider.tracks.push(audioTrack); + }; + + /** + * Plays a given track. + * + * @method play + * @param {number} index The track id + */ + play(index: number) { + if (index===undefined || index > AudioProvider.tracks.length-1) return; + this._current = index; + AudioProvider.tracks[index].play(); + }; + + /** + * Pauses a given track. + * + * @method pause + * @param {number} [index] The track id, or if undefined it will pause whichever track currently playing + */ + pause(index?: number) { + if (this._current===undefined || index > AudioProvider.tracks.length-1) return; + index = index || this._current; + AudioProvider.tracks[index].pause(); + }; + + /** + * Stops a given track. + * + * @method stop + * @param {number} [index] The track id, or if undefined it will stop whichever track currently playing + */ + stop(index?: number) { + if (this._current===undefined || index > AudioProvider.tracks.length-1) return; + index = index || this._current; + AudioProvider.tracks[index].stop(); + this._current = undefined; + }; + seekTo(time){ + var index; + AudioProvider.tracks[index].seekTo(time); + } + /** + * Gets an array of tracks managed by this provider + * + * @property tracks + * @readonly + * @type {IAudioTrack[]} + */ + public get tracks() : IAudioTrack[] { + return AudioProvider.tracks; + } + + /** + * Gets current track id + * + * @property current + * @type {number} + */ + public get current() : number { + return this._current; + } + + /** + * Sets current track id + * + * @property current + */ + public set current(v : number) { + this._current = v; + } + +} + +/** + * Creates an HTML5 audio provider + * + * @export + * @class WebAudioProvider + * @constructor + * @extends {AudioProvider} + */ +@Injectable() +export class WebAudioProvider extends AudioProvider { + public audioTrack; + constructor() { + super(); + console.log('Using Web Audio provider'); + } + + create(track: ITrackConstraint) { + this.audioTrack = new WebAudioTrack(track.src, track.preload); + Object.assign(this.audioTrack, track); + let trackId = WebAudioProvider.tracks.push(this.audioTrack); + this.audioTrack.id = trackId-1; + return this.audioTrack; + } +} + +/** + * Creates a Cordova audio provider + * + * @export + * @class CordovaMediaProvider + * @constructor + * @extends {AudioProvider} + */ +@Injectable() +export class CordovaMediaProvider extends AudioProvider { + + constructor() { + super(); + console.log('Using Cordova Media provider'); + } + + create(track: ITrackConstraint) { + let audioTrack = new CordovaAudioTrack(track.src); + Object.assign(audioTrack, track); + let trackId = CordovaMediaProvider.tracks.push(audioTrack); + audioTrack.id = trackId-1; + return audioTrack; + } + } \ No newline at end of file diff --git a/dist/ionic-audio-time-pipe.ts b/dist/ionic-audio-time-pipe.ts old mode 100644 new mode 100755 index 4c798d9..b39a95f --- a/dist/ionic-audio-time-pipe.ts +++ b/dist/ionic-audio-time-pipe.ts @@ -1,27 +1,27 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -/** - * A pipe to convert milliseconds to a string representation - * - * @export - * @class AudioTimePipe - * @implements {PipeTransform} - */ -@Pipe({name: 'audioTime'}) -export class AudioTimePipe implements PipeTransform { - - /** - * Transforms milliseconds to hh:mm:ss - * - * @method transform - * @param {number} [value] The milliseconds - * @return {string} hh:mm:ss - */ - transform(value?:number) : string { - if (value===undefined || Number.isNaN(value)) return ''; - let s = Math.trunc(value % 60); - let m = Math.trunc((value / 60) % 60); - let h = Math.trunc(((value / 60) / 60) % 60); - return h > 0 ? `${h<10?'0'+h:h}:${m<10?'0'+m:m}:${s<10?'0'+s:s}` : `${m<10?'0'+m:m}:${s<10?'0'+s:s}`; - } +import {Pipe, PipeTransform} from '@angular/core'; + +/** + * A pipe to convert milliseconds to a string representation + * + * @export + * @class AudioTimePipe + * @implements {PipeTransform} + */ +@Pipe({name: 'audioTime'}) +export class AudioTimePipe implements PipeTransform { + + /** + * Transforms milliseconds to hh:mm:ss + * + * @method transform + * @param {number} [value] The milliseconds + * @return {string} hh:mm:ss + */ + transform(value?:number) : string { + if (value===undefined || Number.isNaN(value)) return ''; + let s = Math.trunc(value % 60); + let m = Math.trunc((value / 60) % 60); + let h = Math.trunc(((value / 60) / 60) % 60); + return h > 0 ? `${h<10?'0'+h:h}:${m<10?'0'+m:m}:${s<10?'0'+s:s}` : `${m<10?'0'+m:m}:${s<10?'0'+s:s}`; + } } \ No newline at end of file diff --git a/dist/ionic-audio-track-component.ts b/dist/ionic-audio-track-component.ts old mode 100644 new mode 100755 index cb51e72..210f8c0 --- a/dist/ionic-audio-track-component.ts +++ b/dist/ionic-audio-track-component.ts @@ -1,156 +1,170 @@ -import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; -import {AudioProvider, WebAudioProvider, CordovaMediaProvider} from './ionic-audio-providers'; -import {WebAudioTrack} from './ionic-audio-web-track'; -import {CordovaAudioTrack} from './ionic-audio-cordova-track'; - -import {Component, Directive, DoCheck, SimpleChange, EventEmitter, ElementRef, Renderer, Output, Input, Injectable, Inject, Optional, Pipe, PipeTransform} from '@angular/core'; - - -/** - * # `````` - * - * Creates a top level audio-track component - * - * ## Usage - * - * ```` - * - * ... - * - * ```` - * @element audio-track - * @export - * @class AudioTrackComponent - */ -@Component({ - selector: 'audio-track', - template: '', - providers: [] -}) -export class AudioTrackComponent { - /** - * Input property containing a JSON object with at least a src property - * ```` - * this.myTrack = { - * src: 'https://www,mysite.com/myTrack.mp3', - * artist: 'Artist name', - * title: '...', - * art: 'img/artist.jpg', - * preload: 'metadata' // tell the plugin to preload metadata such as duration for this track - * }; - * ```` - * @property track - * @type {ITrackConstraint} - */ - @Input() track: ITrackConstraint; - - /** - * Output property expects an event handler to be notified whenever playback finishes - * - * @property onFinish - * @type {EventEmitter} - */ - @Output() onFinish = new EventEmitter(); - - private _isFinished: boolean = false; - private _audioTrack: IAudioTrack; - - constructor(private _audioProvider: AudioProvider) {} - - ngOnInit() { - if (!(this.track instanceof WebAudioTrack) && !(this.track instanceof CordovaAudioTrack)) { - this._audioTrack = this._audioProvider.create(this.track); - } else { - Object.assign(this._audioTrack, this.track); - this._audioProvider.add(this._audioTrack); - } - - // update input track parameter with track is so we pass it to WebAudioProvider if needed - this.track.id = this._audioTrack.id; - } - - play() { - this._audioTrack.play(); - this._audioProvider.current = this._audioTrack.id; - } - - pause() { - this._audioTrack.pause(); - this._audioProvider.current = undefined; - } - - toggle() { - if (this._audioTrack.isPlaying) { - this.pause(); - } else { - this.play(); - } - } - - seekTo(time:number) { - this._audioTrack.seekTo(time); - } - - - public get id() : number { - return this._audioTrack.id; - } - - public get art() : string { - return this.track.art; - } - - - public get artist() : string { - return this.track.artist; - } - - - public get title() : string { - return this.track.title; - } - - public get progress() : number { - return this._audioTrack.progress; - } - - public get isPlaying() : boolean { - return this._audioTrack.isPlaying; - } - - public get duration() : number { - return this._audioTrack.duration; - } - - public get completed() : number { - return this._audioTrack.completed; - } - - public get canPlay() { - return this._audioTrack.canPlay; - } - - public get error() { - return this._audioTrack.error; - } - - public get isLoading() : boolean { - return this._audioTrack.isLoading; - } - - public get hasLoaded() : boolean { - return this.hasLoaded; - } - - ngDoCheck() { - if(!Object.is(this._audioTrack.isFinished, this._isFinished)) { - // some logic here to react to the change - this._isFinished = this._audioTrack.isFinished; - - // track has stopped, trigger finish event - if (this._isFinished) { - this.onFinish.emit(this.track); - } - } - } +import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; +import {AudioProvider, WebAudioProvider, CordovaMediaProvider} from './ionic-audio-providers'; +import {WebAudioTrack} from './ionic-audio-web-track'; +import {CordovaAudioTrack} from './ionic-audio-cordova-track'; + +import {Component, Directive, DoCheck, SimpleChange, EventEmitter, ElementRef, Renderer, Output, Input, Injectable, Inject, Optional, Pipe, PipeTransform} from '@angular/core'; + + +/** + * # `````` + * + * Creates a top level audio-track component + * + * ## Usage + * + * ```` + * + * ... + * + * ```` + * @element audio-track + * @export + * @class AudioTrackComponent + */ +@Component({ + selector: 'audio-track', + template: '', + providers: [] +}) +export class AudioTrackComponent { + /** + * Input property containing a JSON object with at least a src property + * ```` + * this.myTrack = { + * src: 'https://www,mysite.com/myTrack.mp3', + * artist: 'Artist name', + * title: '...', + * art: 'img/artist.jpg', + * preload: 'metadata' // tell the plugin to preload metadata such as duration for this track + * }; + * ```` + * @property track + * @type {ITrackConstraint} + */ + @Input() track: ITrackConstraint; + + /** + * Output property expects an event handler to be notified whenever playback finishes + * + * @property onFinish + * @type {EventEmitter} + */ + @Output() onFinish = new EventEmitter(); + + private _isFinished: boolean = false; + private _audioTrack: IAudioTrack; + + constructor(private _audioProvider: AudioProvider) {} + + ngOnInit() { + if (!(this.track instanceof WebAudioTrack) && !(this.track instanceof CordovaAudioTrack)) { + this._audioTrack = this._audioProvider.create(this.track); + } else { + Object.assign(this._audioTrack, this.track); + this._audioProvider.add(this._audioTrack); + } + + // update input track parameter with track is so we pass it to WebAudioProvider if needed + this.track.id = this._audioTrack.id; + } + + play() { + this._audioTrack.play(); + this._audioProvider.current = this._audioTrack.id; + } + + pause() { + this._audioTrack.pause(); + this._audioProvider.current = undefined; + } + + toggle() { + if (this._audioTrack.isPlaying) { + this.pause(); + } else { + this.play(); + } + } + + + seekTo(time:number) { + this._audioTrack.seekTo(time); + } + + + public get id() : number { + return this._audioTrack.id; + } + + public get art() : string { + return this.track.art; + } + + + public get artist() : string { + return this.track.artist; + } + + + public get title() : string { + return this.track.title; + } + + public get progress() : number { + return this._audioTrack.progress; + } + + public get isPlaying() : boolean { + return this._audioTrack.isPlaying; + } + + public get duration() : number { + return this._audioTrack.duration; + } + + public get completed() : number { + return this._audioTrack.completed; + } + + public get canPlay() { + return this._audioTrack.canPlay; + } + + // public get error() { + // return this._audioTrack.error; + // } + + public get isLoading() : boolean { + return this._audioTrack.isLoading; + } + + public get hasLoaded() : boolean { + return this.hasLoaded; + } + + ngDoCheck() { + if(!Object.is(this._audioTrack.isFinished, this._isFinished)) { + // some logic here to react to the change + this._isFinished = this._audioTrack.isFinished; + + // track has stopped, trigger finish event + if (this._isFinished) { + this.onFinish.emit(this.track); + } + } + } + ngOnChanges(change) { + if(change.track) { + if (!(this.track instanceof WebAudioTrack) && !(this.track instanceof CordovaAudioTrack)) { + this._audioTrack = this._audioProvider.create(this.track); + } else { + Object.assign(this._audioTrack, this.track); + this._audioProvider.add(this._audioTrack); + } + + // update input track parameter with track is so we pass it to WebAudioProvider if needed + this.track.id = this._audioTrack.id; + } + } } \ No newline at end of file diff --git a/dist/ionic-audio-track-play-component.ts b/dist/ionic-audio-track-play-component.ts old mode 100644 new mode 100755 index 540ca73..f095ffc --- a/dist/ionic-audio-track-play-component.ts +++ b/dist/ionic-audio-track-play-component.ts @@ -1,92 +1,92 @@ -import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; - -import {Component, Directive, DoCheck, SimpleChange, EventEmitter, ElementRef, Renderer, Output, Input, Injectable, Inject, Optional, Pipe, PipeTransform} from '@angular/core'; - -/** - * # `````` - * - * Renders a play/pause button that optionally displays a loading spinner - * - * ## Usage - * ```` - * - * - * - *

{{audio.title}}

- *
- *
- * ```` - * If placed within a `````` component it will render as a semi-transparent button layover (see live demo). - * Passing a `````` as a child element will display a loading spinner while loading. - * - * ```` - * - * - * - * - * - * - *

{{audio.title}}

- *
- *
- * ```` - * - * @element audio-track-play - * @parents audio-track - * @export - * @class AudioTrackPlayComponent - */ -@Component({ - selector: 'audio-track-play', - template: ` - - ` -}) -export class AudioTrackPlayComponent { - private _isPlaying: boolean = false; - private _isLoading: boolean = false; - - /** - * The AudioTrackComponent parent instance created by `````` - * - * @property @Input() audioTrack - * @type {IAudioTrack} - */ - @Input() audioTrack: IAudioTrack; - - /** - * Renders the component using the light theme - * - * @property @Input() light - * @type {boolean} - */ - @Input() - set light(val: boolean) { - this.el.nativeElement.firstElementChild.classList.add('light'); - } - - /** - * Renders the component using the dark theme - * - * @property @Input() dark - * @type {boolean} - */ - @Input() - set dark(val: boolean) { - this.el.nativeElement.firstElementChild.classList.add('dark'); - } - - constructor(private el: ElementRef) {} - - toggle(){ - if (this.audioTrack.isPlaying) { - this.audioTrack.pause() - } else { - this.audioTrack.play() - } - } +import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; + +import {Component, Directive, DoCheck, SimpleChange, EventEmitter, ElementRef, Renderer, Output, Input, Injectable, Inject, Optional, Pipe, PipeTransform} from '@angular/core'; + +/** + * # `````` + * + * Renders a play/pause button that optionally displays a loading spinner + * + * ## Usage + * ```` + * + * + * + *

{{audio.title}}

+ *
+ *
+ * ```` + * If placed within a `````` component it will render as a semi-transparent button layover (see live demo). + * Passing a `````` as a child element will display a loading spinner while loading. + * + * ```` + * + * + * + * + * + * + *

{{audio.title}}

+ *
+ *
+ * ```` + * + * @element audio-track-play + * @parents audio-track + * @export + * @class AudioTrackPlayComponent + */ +@Component({ + selector: 'audio-track-play', + template: ` + + ` +}) +export class AudioTrackPlayComponent { + private _isPlaying: boolean = false; + private _isLoading: boolean = false; + + /** + * The AudioTrackComponent parent instance created by `````` + * + * @property @Input() audioTrack + * @type {IAudioTrack} + */ + @Input() audioTrack: IAudioTrack; + + /** + * Renders the component using the light theme + * + * @property @Input() light + * @type {boolean} + */ + @Input() + set light(val: boolean) { + this.el.nativeElement.firstElementChild.classList.add('light'); + } + + /** + * Renders the component using the dark theme + * + * @property @Input() dark + * @type {boolean} + */ + @Input() + set dark(val: boolean) { + this.el.nativeElement.firstElementChild.classList.add('dark'); + } + + constructor(private el: ElementRef) {} + + toggle(){ + if (this.audioTrack.isPlaying) { + this.audioTrack.pause() + } else { + this.audioTrack.play() + } + } } \ No newline at end of file diff --git a/dist/ionic-audio-track-progress-component.ts b/dist/ionic-audio-track-progress-component.ts old mode 100644 new mode 100755 index 9819f69..c5734a1 --- a/dist/ionic-audio-track-progress-component.ts +++ b/dist/ionic-audio-track-progress-component.ts @@ -1,116 +1,124 @@ -import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; -import {AudioTimePipe} from './ionic-audio-time-pipe'; - -import {Component, Directive, DoCheck, SimpleChange, EventEmitter, ElementRef, Renderer, Output, Input, Injectable, Inject, Optional, Pipe, PipeTransform} from '@angular/core'; -import {NgStyle} from '@angular/common'; - -/** - * # `````` - * - * Renders a timer component displaying track progress and duration - * - * ## Usage - * ```` - * - * ```` - * - * @element audio-track-progress - * @parents audio-track - * @export - * @class AudioTrackProgressComponent - */ -@Component({ - selector: 'audio-track-progress', - template: '{{audioTrack.progress | audioTime}} / {{audioTrack.duration | audioTime}}', - pipes: [AudioTimePipe] -}) -export class AudioTrackProgressComponent { - /** - * The AudioTrackComponent parent instance created by `````` - * - * @property @Input() audioTrack - * @type {IAudioTrack} - */ - @Input() audioTrack: IAudioTrack; -} - -/** - * # `````` - * - * Renders a progress bar with optional timer, duration and progress indicator - * - * ## Usage - * ```` - * - * ```` - * - * @element audio-track-progress-bar - * @parents audio-track - * @export - * @class AudioTrackProgressBarComponent - */ -@Component({ - selector: 'audio-track-progress-bar', - template: ` - - - - - `, - pipes: [AudioTimePipe], - directives: [NgStyle] -}) -export class AudioTrackProgressBarComponent { - /** - * The AudioTrackComponent parent instance created by `````` - * - * @property @Input() audioTrack - * @type {IAudioTrack} - */ - @Input() audioTrack: IAudioTrack; - - private _completed: number = 0; - private _range: number = 0; - private _showDuration: boolean; - private _showProgress: boolean; - constructor(private el: ElementRef, private renderer: Renderer) { - } - - /** - * Input property indicating whether to display track progress - * - * @property @Input() progress - * @type {boolean} - */ - @Input() - public set progress(v : boolean) { - this._showProgress = true; - } - - /** - * Input property indicating whether to display track duration - * - * @property @Input() duration - * @type {boolean} - */ - @Input() - public set duration(v: boolean) { - this._showDuration = true; - } - - ngOnInit() { - this.renderer.setElementStyle(this.el.nativeElement, 'width', '100%'); - } - - ngDoCheck() { - if(this.audioTrack.completed > 0 && !Object.is(this.audioTrack.completed, this._completed)) { - this._completed = this.audioTrack.completed; - this._range = Math.round(this._completed*100*100)/100; - } - } - - seekTo() { - let seekTo: number = Math.round(this.audioTrack.duration*this._range)/100; - if (!Number.isNaN(seekTo)) this.audioTrack.seekTo(seekTo); - } +import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; +import {AudioTimePipe} from './ionic-audio-time-pipe'; + +import {Component, Directive, DoCheck, SimpleChange, EventEmitter, ElementRef, Renderer, Output, Input, Injectable, Inject, Optional, Pipe, PipeTransform} from '@angular/core'; +import {NgStyle} from '@angular/common'; + +/** + * # `````` + * + * Renders a timer component displaying track progress and duration + * + * ## Usage + * ```` + * + * ```` + * + * @element audio-track-progress + * @parents audio-track + * @export + * @class AudioTrackProgressComponent + */ +@Component({ + selector: 'audio-track-progress', + template: '{{audioTrack.progress | audioTime}} / {{audioTrack.duration | audioTime}}', + pipes: [AudioTimePipe] +}) +export class AudioTrackProgressComponent { + /** + * The AudioTrackComponent parent instance created by `````` + * + * @property @Input() audioTrack + * @type {IAudioTrack} + */ + @Input() audioTrack: IAudioTrack; +} + +/** + * # `````` + * + * Renders a progress bar with optional timer, duration and progress indicator + * + * ## Usage + * ```` + * + * ```` + * + * @element audio-track-progress-bar + * @parents audio-track + * @export + * @class AudioTrackProgressBarComponent + */ +@Component({ + selector: 'audio-track-progress-bar', + template: ` + + + + + `, + pipes: [AudioTimePipe], + directives: [NgStyle] +}) +export class AudioTrackProgressBarComponent { + /** + * The AudioTrackComponent parent instance created by `````` + * + * @property @Input() audioTrack + * @type {IAudioTrack} + */ + @Input() audioTrack: IAudioTrack; + + private _completed: number = 0; + private _range: number = 0; + private _showDuration: boolean; + private _showProgress: boolean; + constructor(private el: ElementRef, private renderer: Renderer) { + } + + /** + * Input property indicating whether to display track progress + * + * @property @Input() progress + * @type {boolean} + */ + @Input() + public set progress(v : boolean) { + this._showProgress = true; + } + + /** + * Input property indicating whether to display track duration + * + * @property @Input() duration + * @type {boolean} + */ + @Input() + public set duration(v: boolean) { + this._showDuration = true; + } + + ngOnInit() { + this.renderer.setElementStyle(this.el.nativeElement, 'width', '100%'); + } + + ngDoCheck() { + if(this.audioTrack.completed > 0 && !Object.is(this.audioTrack.completed, this._completed)) { + this._completed = this.audioTrack.completed; + this._range = Math.round(this._completed*100*100)/100; + } + } + + seekTo() { + let seekTo: number = Math.round(this.audioTrack.duration*this._range)/100; + if (!Number.isNaN(seekTo)) this.audioTrack.seekTo(seekTo); + } + + ngOnChanges(change){ + console.log(change); + if(change.audioTrack){ + this._completed=0; + this._range=0; + } + } } \ No newline at end of file diff --git a/dist/ionic-audio-track-progress-slider-component.ts b/dist/ionic-audio-track-progress-slider-component.ts old mode 100644 new mode 100755 index 8156341..9e7eaab --- a/dist/ionic-audio-track-progress-slider-component.ts +++ b/dist/ionic-audio-track-progress-slider-component.ts @@ -1,42 +1,50 @@ -import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; - -import {Component, Directive, DoCheck, SimpleChange, EventEmitter, ElementRef, Renderer, Output, Input, Injectable, Inject, Optional, Pipe, PipeTransform} from '@angular/core'; -import {DragGesture} from 'ionic-angular/gestures/drag-gesture'; -import {NgStyle} from '@angular/common'; - -@Component({ - selector: 'audio-track-progress-slider', - template: ``, - directives: [NgStyle] -}) -export class AudioTrackProgressSliderComponent extends DragGesture { - @Input() audioTrack: IAudioTrack; - @Output() onSeek = new EventEmitter(); - private _completed: number = 0; - constructor(private el: ElementRef) { - super(el.nativeElement); - } - - ngOnInit() { - super.listen(); - } - - ngDoCheck() { - if(this.audioTrack.completed > 0 && !Object.is(this.audioTrack.completed, this._completed)) { - - } - } - - onDrag(ev) { - // console.log(ev) - return super.onDrag(ev); - }; - onDragStart(ev) { - // console.log(ev); - return super.onDragStart(ev); - }; - onDragEnd(ev) { - this.onSeek.emit(ev); - return super.onDragEnd(ev); - }; +import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; + +import {Component, Directive, DoCheck, SimpleChange, EventEmitter, ElementRef, Renderer, Output, Input, Injectable, Inject, Optional, Pipe, PipeTransform} from '@angular/core'; +import {DragGesture} from 'ionic-angular/gestures/drag-gesture'; +import {NgStyle} from '@angular/common'; + +@Component({ + selector: 'audio-track-progress-slider', + template: ``, + directives: [NgStyle] +}) +export class AudioTrackProgressSliderComponent extends DragGesture { + @Input() audioTrack: IAudioTrack; + @Output() onSeek = new EventEmitter(); + private _completed: number = 0; + constructor(private el: ElementRef) { + super(el.nativeElement); + } + + ngOnInit() { + super.listen(); + } + + ngDoCheck() { + if(this.audioTrack.completed > 0 && !Object.is(this.audioTrack.completed, this._completed)) { + + } + } + + onDrag(ev) { + // console.log(ev) + return super.onDrag(ev); + }; + onDragStart(ev) { + // console.log(ev); + return super.onDragStart(ev); + }; + onDragEnd(ev) { + this.onSeek.emit(ev); + return super.onDragEnd(ev); + }; + + ngOnChanges(change){ + console.log(change); + if(change.audioTrack){ + this._completed=0; + //this._range=0; + } + } } \ No newline at end of file diff --git a/dist/ionic-audio-web-track.ts b/dist/ionic-audio-web-track.ts old mode 100644 new mode 100755 index 51717d2..dc06e2e --- a/dist/ionic-audio-web-track.ts +++ b/dist/ionic-audio-web-track.ts @@ -1,250 +1,250 @@ -import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; -import {Injectable, Inject, Optional} from '@angular/core'; - -declare let window; -window.AudioContext = window['AudioContext'] || window['webkitAudioContext']; - -/** - * Creates an HTML5 audio track - * - * @export - * @class WebAudioTrack - * @constructor - * @implements {IAudioTrack} - */ -@Injectable() -export class WebAudioTrack implements IAudioTrack { - private audio: HTMLAudioElement; - public isPlaying: boolean = false; - public isFinished: boolean = false; - private _progress: number = 0; - private _completed: number = 0; - private _duration: number; - private _id: number; - private _isLoading: boolean; - private _hasLoaded: boolean; - constructor(public src: string, @Optional() public preload: string = 'none', @Optional() private ctx: AudioContext = undefined) { - // audio context not needed for now - // this.ctx = this.ctx || new AudioContext(); - - this.createAudio(); - } - - private createAudio() { - this.audio = new Audio(); - this.audio.src = this.src; - this.audio.preload = this.preload; - //this.audio.controls = true; - //this.audio.autoplay = false; - - this.audio.addEventListener("timeupdate", (e) => { this.onTimeUpdate(e); }, false); - - this.audio.addEventListener("error", (err) => { - console.log(`Audio error => track ${this.src}`, err); - this.isPlaying = false; - }, false); - - this.audio.addEventListener("canplay", () => { - console.log(`Loaded track ${this.src}`); - this._isLoading = false; - this._hasLoaded = true; - }, false); - - this.audio.addEventListener("playing", () => { - console.log(`Playing track ${this.src}`); - this.isFinished = false; - this.isPlaying = true; - }, false); - - this.audio.addEventListener("ended", () => { - this.isPlaying = false; - this.isFinished = true; - console.log('Finished playback'); - }, false); - - this.audio.addEventListener("durationchange", (e:any) => { - this._duration = e.target.duration; - }, false); - } - - private onTimeUpdate(e: Event) { - if (this.isPlaying && this.audio.currentTime > 0) { - this._progress = this.audio.currentTime; - this._completed = this.audio.duration > 0 ? Math.trunc (this.audio.currentTime / this.audio.duration * 100)/100 : 0; - } - } - - static formatTime(value:number) { - let s = Math.trunc(value % 60); - let m = Math.trunc((value / 60) % 60); - let h = Math.trunc(((value / 60) / 60) % 60); - return h > 0 ? `${h<10?'0'+h:h}:${m<10?'0'+m:m}:${s<10?'0'+s:s}` : `${m<10?'0'+m:m}:${s<10?'0'+s:s}`; - } - - - /** - * Gets the track id - * - * @property id - * @type {number} - */ - public get id() : number { - return this._id; - } - - /** - * Sets the track id - * - * @property id - */ - public set id(v : number) { - this._id = v; - } - - /** - * Gets the track duration, or -1 if it cannot be determined - * - * @property duration - * @readonly - * @type {number} - */ - public get duration() : number { - return this._duration; - } - - /** - * Gets current track time (progress) - * - * @property progress - * @readonly - * @type {number} - */ - public get progress() : number { - return this._progress; - } - - /** - * Gets current track progress as a percentage - * - * @property completed - * @readonly - * @type {number} - */ - public get completed() : number { - return this._completed; - } - - /** - * Gets any errors logged by HTML5 audio - * - * @property error - * @readonly - * @type {MediaError} - */ - public get error() : MediaError { - return this.audio.error; - } - - /** - * Gets a boolean value indicating whether the current source can be played - * - * @property canPlay - * @readonly - * @type {boolean} - */ - public get canPlay() : boolean { - let format = `audio/${this.audio.src.substr(this.audio.src.lastIndexOf('.')+1)}`; - return this.audio && this.audio.canPlayType(format) != ''; - } - - - /** - * Gets a boolean value indicating whether the track is in loading state - * - * @property isLoading - * @readonly - * @type {boolean} - */ - public get isLoading() : boolean { - return this._isLoading; - } - - - /** - * Gets a boolean value indicating whether the track has finished loading - * - * @property hadLoaded - * @readonly - * @type {boolean} - */ - public get hasLoaded() : boolean { - return this._hasLoaded; - } - - - /** - * Plays current track - * - * @method play - */ - play() { - if (!this.audio) { - this.createAudio(); - } - - if (!this._hasLoaded) { - console.log(`Loading track ${this.src}`); - this._isLoading = true; - } - - //var source = this.ctx.createMediaElementSource(this.audio); - //source.connect(this.ctx.destination); - this.audio.play(); - } - - /** - * Pauses current track - * - * @method pause - */ - pause() { - if (!this.isPlaying) return; - console.log(`Pausing track ${this.src}`); - this.audio.pause(); - this.isPlaying = false; - } - - /** - * Stops current track and releases audio - * - * @method stop - */ - stop() { - if (!this.audio) return; - this.pause(); - this.audio.removeEventListener("timeupdate", (e) => { this.onTimeUpdate(e); }); - this.isFinished = true; - this.destroy(); - } - - - /** - * Seeks to a new position within the track - * - * @method seekTo - * @param {number} time the new position to seek to - */ - seekTo(time: number) { - this.audio.currentTime = time; - } - - /** - * Releases audio resources - * - * @method destroy - */ - destroy() { - this.audio = undefined; - console.log(`Released track ${this.src}`); - } +import {IAudioProvider, ITrackConstraint, IAudioTrack} from './ionic-audio-interfaces'; +import {Injectable, Inject, Optional} from '@angular/core'; + +declare let window; +window.AudioContext = window['AudioContext'] || window['webkitAudioContext']; + +/** + * Creates an HTML5 audio track + * + * @export + * @class WebAudioTrack + * @constructor + * @implements {IAudioTrack} + */ +@Injectable() +export class WebAudioTrack implements IAudioTrack { + private audio: HTMLAudioElement; + public isPlaying: boolean = false; + public isFinished: boolean = false; + private _progress: number = 0; + private _completed: number = 0; + private _duration: number; + private _id: number; + private _isLoading: boolean; + private _hasLoaded: boolean; + constructor(public src: string, @Optional() public preload: string = 'none', @Optional() private ctx: AudioContext = undefined) { + // audio context not needed for now + // this.ctx = this.ctx || new AudioContext(); + + this.createAudio(); + } + + private createAudio() { + this.audio = new Audio(); + this.audio.src = this.src; + this.audio.preload = this.preload; + //this.audio.controls = true; + //this.audio.autoplay = false; + + this.audio.addEventListener("timeupdate", (e) => { this.onTimeUpdate(e); }, false); + + this.audio.addEventListener("error", (err) => { + console.log(`Audio error => track ${this.src}`, err); + this.isPlaying = false; + }, false); + + this.audio.addEventListener("canplay", () => { + console.log(`Loaded track ${this.src}`); + this._isLoading = false; + this._hasLoaded = true; + }, false); + + this.audio.addEventListener("playing", () => { + console.log(`Playing track ${this.src}`); + this.isFinished = false; + this.isPlaying = true; + }, false); + + this.audio.addEventListener("ended", () => { + this.isPlaying = false; + this.isFinished = true; + console.log('Finished playback'); + }, false); + + this.audio.addEventListener("durationchange", (e:any) => { + this._duration = e.target.duration; + }, false); + } + + private onTimeUpdate(e: Event) { + if (this.isPlaying && this.audio.currentTime > 0) { + this._progress = this.audio.currentTime; + this._completed = this.audio.duration > 0 ? Math.trunc (this.audio.currentTime / this.audio.duration * 100)/100 : 0; + } + } + + static formatTime(value:number) { + let s = Math.trunc(value % 60); + let m = Math.trunc((value / 60) % 60); + let h = Math.trunc(((value / 60) / 60) % 60); + return h > 0 ? `${h<10?'0'+h:h}:${m<10?'0'+m:m}:${s<10?'0'+s:s}` : `${m<10?'0'+m:m}:${s<10?'0'+s:s}`; + } + + + /** + * Gets the track id + * + * @property id + * @type {number} + */ + public get id() : number { + return this._id; + } + + /** + * Sets the track id + * + * @property id + */ + public set id(v : number) { + this._id = v; + } + + /** + * Gets the track duration, or -1 if it cannot be determined + * + * @property duration + * @readonly + * @type {number} + */ + public get duration() : number { + return this._duration; + } + + /** + * Gets current track time (progress) + * + * @property progress + * @readonly + * @type {number} + */ + public get progress() : number { + return this._progress; + } + + /** + * Gets current track progress as a percentage + * + * @property completed + * @readonly + * @type {number} + */ + public get completed() : number { + return this._completed; + } + + /** + * Gets any errors logged by HTML5 audio + * + * @property error + * @readonly + * @type {MediaError} + */ + public get error() : MediaError { + return this.audio.error; + } + + /** + * Gets a boolean value indicating whether the current source can be played + * + * @property canPlay + * @readonly + * @type {boolean} + */ + public get canPlay() : boolean { + let format = `audio/${this.audio.src.substr(this.audio.src.lastIndexOf('.')+1)}`; + return this.audio && this.audio.canPlayType(format) != ''; + } + + + /** + * Gets a boolean value indicating whether the track is in loading state + * + * @property isLoading + * @readonly + * @type {boolean} + */ + public get isLoading() : boolean { + return this._isLoading; + } + + + /** + * Gets a boolean value indicating whether the track has finished loading + * + * @property hadLoaded + * @readonly + * @type {boolean} + */ + public get hasLoaded() : boolean { + return this._hasLoaded; + } + + + /** + * Plays current track + * + * @method play + */ + play() { + if (!this.audio) { + this.createAudio(); + } + + if (!this._hasLoaded) { + console.log(`Loading track ${this.src}`); + this._isLoading = true; + } + + //var source = this.ctx.createMediaElementSource(this.audio); + //source.connect(this.ctx.destination); + this.audio.play(); + } + + /** + * Pauses current track + * + * @method pause + */ + pause() { + if (!this.isPlaying) return; + console.log(`Pausing track ${this.src}`); + this.audio.pause(); + this.isPlaying = false; + } + + /** + * Stops current track and releases audio + * + * @method stop + */ + stop() { + if (!this.audio) return; + this.pause(); + this.audio.removeEventListener("timeupdate", (e) => { this.onTimeUpdate(e); }); + this.isFinished = true; + this.destroy(); + } + + + /** + * Seeks to a new position within the track + * + * @method seekTo + * @param {number} time the new position to seek to + */ + seekTo(time: number) { + this.audio.currentTime = time; + } + + /** + * Releases audio resources + * + * @method destroy + */ + destroy() { + this.audio = undefined; + console.log(`Released track ${this.src}`); + } } \ No newline at end of file diff --git a/dist/ionic-audio.scss b/dist/ionic-audio.scss old mode 100644 new mode 100755 index a9bda8a..0365149 --- a/dist/ionic-audio.scss +++ b/dist/ionic-audio.scss @@ -1,197 +1,197 @@ - -// *** styles for audio slider component - -// Styling Cross-Browser Compatible Range Inputs with Sass -// Github: https://github.com/darlanrod/input-range-sass -// Author: Darlan Rod https://github.com/darlanrod -// Version 1.0.1 -// MIT License - -$track-color: map-get($colors, primary); -$thumb-color: map-get($colors, light); - -$thumb-radius: 50%; -$thumb-height: 20px; -$thumb-width: 20px; -$thumb-shadow-size: 1px; -$thumb-shadow-blur: 1px; -$thumb-shadow-color: #111; -$thumb-border-width: 1px; -$thumb-border-color: #fff; - -$track-width: 50%; -$track-height: 5px; -$track-shadow-size: 2px; -$track-shadow-blur: 2px; -$track-shadow-color: #222; -$track-border-width: 1px; -$track-border-color: #000; - -$track-radius: 5px; -$contrast: 5%; - -@mixin shadow($shadow-size, $shadow-blur, $shadow-color) { - box-shadow: $shadow-size $shadow-size $shadow-blur $shadow-color, 0 0 $shadow-size lighten($shadow-color, 5%); -} - -@mixin track() { - width: $track-width; - height: $track-height; - cursor: pointer; - transition: all .2s ease; -} - -@mixin thumb() { - @include shadow($thumb-shadow-size, $thumb-shadow-blur, $thumb-shadow-color); - //border: $thumb-border-width solid $thumb-border-color; - height: $thumb-height; - width: $thumb-width; - border-radius: $thumb-radius; - background: $thumb-color; - cursor: pointer; -} - -// end audio slider component - -audio-track { - - ion-thumbnail { - > img { - top: 0; - position: absolute; - .md & { - @import "../../../node_modules/ionic-angular/components/item/item.md"; - margin-top: ($item-md-padding-right / 2); - } - - .ios & { - @import "../../../node_modules/ionic-angular/components/item/item.ios"; - margin-top: ($item-ios-padding-right / 2); - } - } - audio-track-play { - position: absolute; - top: 0rem; - display: inline-flex; - - .md & { - @import "../../../node_modules/ionic-angular/components/item/item.md"; - min-width: $item-md-thumbnail-size; - min-height: $item-md-thumbnail-size; - margin: ($item-md-padding-right / 2) $item-md-padding-right ($item-md-padding-right / 2) 0; - } - - .ios & { - @import "../../../node_modules/ionic-angular/components/item/item.ios"; - min-width: $item-ios-thumbnail-size; - min-height: $item-ios-thumbnail-size; - margin: ($item-ios-padding-right / 2) $item-ios-padding-right ($item-ios-padding-right / 2) 0; - } - - button { - width: 100% !important; - height: inherit !important; - margin: 0 !important; - - &.light { - background: map-get($colors, dark); - color: map-get($colors, light); - opacity: .8; - } - - &.dark { - color: map-get($colors, dark); - opacity: .6; - } - /* - ion-icon { - font-size: 3em !important; - }*/ - } - - } - } - - audio-track-progress { - em { - font-size: .8em; - } - } - - - audio-track-progress-bar{ - display: block; - - - .ios & { - @import "../../../node_modules/ionic-angular/components/item/item.ios"; - //border: solid 1px $list-ios-border-color; - //border-radius: 5px; - } - - .md & { - @import "../../../node_modules/ionic-angular/components/item/item.md"; - - } - - & > div { - width: 0; - height: 2px; - background: yellow; - border-radius: 5px; - } - - [type=range] { - -webkit-appearance: none; - margin: $thumb-height / 2 0; - width: $track-width; - - &:focus { - outline: none; - } - - &::-webkit-slider-runnable-track { - @include track(); - //@include shadow($track-shadow-size, $track-shadow-blur, $track-shadow-color); - background: $track-color; - //border: $track-border-width solid $track-border-color; - //border-radius: $track-radius; - } - - &::-webkit-slider-thumb { - @include thumb(); - -webkit-appearance: none; - margin-top: ((-$track-border-width * 2 + $track-height) / 2) - ($thumb-height / 2); - } - - &.light { - &::-webkit-slider-runnable-track { - background: map-get($colors, light); - } - - &::-webkit-slider-thumb { - background: map-get($colors, light); - } - } - - &.dark { - &::-webkit-slider-runnable-track { - background: map-get($colors, dark); - } - - &::-webkit-slider-thumb { - background: map-get($colors, light); - } - } - - &:focus::-webkit-slider-runnable-track { - background: lighten($track-color, $contrast); - } - - } - - time { - font-size: .8em; - } - } + +// *** styles for audio slider component + +// Styling Cross-Browser Compatible Range Inputs with Sass +// Github: https://github.com/darlanrod/input-range-sass +// Author: Darlan Rod https://github.com/darlanrod +// Version 1.0.1 +// MIT License + +$track-color: map-get($colors, primary); +$thumb-color: map-get($colors, light); + +$thumb-radius: 50%; +$thumb-height: 20px; +$thumb-width: 20px; +$thumb-shadow-size: 1px; +$thumb-shadow-blur: 1px; +$thumb-shadow-color: #111; +$thumb-border-width: 1px; +$thumb-border-color: #fff; + +$track-width: 50%; +$track-height: 5px; +$track-shadow-size: 2px; +$track-shadow-blur: 2px; +$track-shadow-color: #222; +$track-border-width: 1px; +$track-border-color: #000; + +$track-radius: 5px; +$contrast: 5%; + +@mixin shadow($shadow-size, $shadow-blur, $shadow-color) { + box-shadow: $shadow-size $shadow-size $shadow-blur $shadow-color, 0 0 $shadow-size lighten($shadow-color, 5%); +} + +@mixin track() { + width: $track-width; + height: $track-height; + cursor: pointer; + transition: all .2s ease; +} + +@mixin thumb() { + @include shadow($thumb-shadow-size, $thumb-shadow-blur, $thumb-shadow-color); + //border: $thumb-border-width solid $thumb-border-color; + height: $thumb-height; + width: $thumb-width; + border-radius: $thumb-radius; + background: $thumb-color; + cursor: pointer; +} + +// end audio slider component + +audio-track { + + ion-thumbnail { + > img { + top: 0; + position: absolute; + .md & { + @import "../../../node_modules/ionic-angular/components/item/item.md"; + margin-top: ($item-md-padding-right / 2); + } + + .ios & { + @import "../../../node_modules/ionic-angular/components/item/item.ios"; + margin-top: ($item-ios-padding-right / 2); + } + } + audio-track-play { + position: absolute; + top: 0rem; + display: inline-flex; + + .md & { + @import "../../../node_modules/ionic-angular/components/item/item.md"; + min-width: $item-md-thumbnail-size; + min-height: $item-md-thumbnail-size; + margin: ($item-md-padding-right / 2) $item-md-padding-right ($item-md-padding-right / 2) 0; + } + + .ios & { + @import "../../../node_modules/ionic-angular/components/item/item.ios"; + min-width: $item-ios-thumbnail-size; + min-height: $item-ios-thumbnail-size; + margin: ($item-ios-padding-right / 2) $item-ios-padding-right ($item-ios-padding-right / 2) 0; + } + + button { + width: 100% !important; + height: inherit !important; + margin: 0 !important; + + &.light { + background: map-get($colors, dark); + color: map-get($colors, light); + opacity: .8; + } + + &.dark { + color: map-get($colors, dark); + opacity: .6; + } + /* + ion-icon { + font-size: 3em !important; + }*/ + } + + } + } + + audio-track-progress { + em { + font-size: .8em; + } + } + + + audio-track-progress-bar{ + display: block; + + + .ios & { + @import "../../../node_modules/ionic-angular/components/item/item.ios"; + //border: solid 1px $list-ios-border-color; + //border-radius: 5px; + } + + .md & { + @import "../../../node_modules/ionic-angular/components/item/item.md"; + + } + + & > div { + width: 0; + height: 2px; + background: yellow; + border-radius: 5px; + } + + [type=range] { + -webkit-appearance: none; + margin: $thumb-height / 2 0; + width: $track-width; + + &:focus { + outline: none; + } + + &::-webkit-slider-runnable-track { + @include track(); + //@include shadow($track-shadow-size, $track-shadow-blur, $track-shadow-color); + background: $track-color; + //border: $track-border-width solid $track-border-color; + //border-radius: $track-radius; + } + + &::-webkit-slider-thumb { + @include thumb(); + -webkit-appearance: none; + margin-top: ((-$track-border-width * 2 + $track-height) / 2) - ($thumb-height / 2); + } + + &.light { + &::-webkit-slider-runnable-track { + background: map-get($colors, light); + } + + &::-webkit-slider-thumb { + background: map-get($colors, light); + } + } + + &.dark { + &::-webkit-slider-runnable-track { + background: map-get($colors, dark); + } + + &::-webkit-slider-thumb { + background: map-get($colors, light); + } + } + + &:focus::-webkit-slider-runnable-track { + background: lighten($track-color, $contrast); + } + + } + + time { + font-size: .8em; + } + } } \ No newline at end of file diff --git a/dist/ionic-audio.ts b/dist/ionic-audio.ts old mode 100644 new mode 100755 index c6a3ad2..9e9d98a --- a/dist/ionic-audio.ts +++ b/dist/ionic-audio.ts @@ -1,15 +1,15 @@ - -/** - * An audio player for Ionic 2 and Angular 2 - * - * @module ionic-audio - */ - -export * from './ionic-audio-interfaces'; -export * from './ionic-audio-providers'; -export * from './ionic-audio-web-track'; -export * from './ionic-audio-cordova-track'; -export * from './ionic-audio-track-component'; -export * from './ionic-audio-track-progress-component'; -export * from './ionic-audio-track-play-component'; -export * from './ionic-audio-time-pipe'; + +/** + * An audio player for Ionic 2 and Angular 2 + * + * @module ionic-audio + */ + +export * from './ionic-audio-interfaces'; +export * from './ionic-audio-providers'; +export * from './ionic-audio-web-track'; +export * from './ionic-audio-cordova-track'; +export * from './ionic-audio-track-component'; +export * from './ionic-audio-track-progress-component'; +export * from './ionic-audio-track-play-component'; +export * from './ionic-audio-time-pipe';