diff --git a/examples/master-player/src/app.module.js b/examples/master-player/src/app.module.js index 0cf83cb1..3ca00d2e 100644 --- a/examples/master-player/src/app.module.js +++ b/examples/master-player/src/app.module.js @@ -8,11 +8,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; -var core_1 = require('@angular/core'); -var platform_browser_1 = require('@angular/platform-browser'); -var core_2 = require('videogular2/core'); -var controls_1 = require('videogular2/controls'); -var master_media_1 = require('./master-media'); +var core_1 = require("@angular/core"); +var platform_browser_1 = require("@angular/platform-browser"); +var core_2 = require("videogular2/core"); +var controls_1 = require("videogular2/controls"); +var master_media_1 = require("./master-media"); +var buffering_1 = require("videogular2/buffering"); var AppModule = (function () { function AppModule() { } @@ -21,7 +22,8 @@ var AppModule = (function () { imports: [ platform_browser_1.BrowserModule, core_2.VgCore, - controls_1.VgControlsModule + controls_1.VgControlsModule, + buffering_1.VgBufferingModule ], declarations: [master_media_1.MasterMedia], bootstrap: [master_media_1.MasterMedia] diff --git a/examples/master-player/src/app.module.js.map b/examples/master-player/src/app.module.js.map index d714c3f1..044f2823 100644 --- a/examples/master-player/src/app.module.js.map +++ b/examples/master-player/src/app.module.js.map @@ -1 +1 @@ -{"version":3,"file":"app.module.js","sourceRoot":"","sources":["app.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,qBAAuB,eAAe,CAAC,CAAA;AACvC,iCAA4B,2BAA2B,CAAC,CAAA;AACxD,qBAAqB,kBAAkB,CAAC,CAAA;AACxC,yBAA+B,sBAAsB,CAAC,CAAA;AACtD,6BAA0B,gBAAgB,CAAC,CAAA;AAW3C;IAAA;IACA,CAAC;IAVD;QAAC,eAAQ,CAAC;YACN,OAAO,EAAE;gBACL,gCAAa;gBACb,aAAM;gBACN,2BAAgB;aACnB;YACD,YAAY,EAAE,CAAC,0BAAW,CAAC;YAC3B,SAAS,EAAE,CAAC,0BAAW,CAAC;SAC3B,CAAC;;iBAAA;IAEF,gBAAC;AAAD,CAAC,AADD,IACC;AADY,iBAAS,YACrB,CAAA"} \ No newline at end of file +{"version":3,"file":"app.module.js","sourceRoot":"","sources":["app.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,qBAAyB,eAAe,CAAC,CAAA;AACzC,iCAA8B,2BAA2B,CAAC,CAAA;AAC1D,qBAAuB,kBAAkB,CAAC,CAAA;AAC1C,yBAAiC,sBAAsB,CAAC,CAAA;AACxD,6BAA4B,gBAAgB,CAAC,CAAA;AAC7C,0BAAkC,uBAAuB,CAAC,CAAA;AAY1D;IAAA;IACA,CAAC;IAXD;QAAC,eAAQ,CAAC;YACN,OAAO,EAAE;gBACL,gCAAa;gBACb,aAAM;gBACN,2BAAgB;gBAChB,6BAAiB;aACpB;YACD,YAAY,EAAE,CAAE,0BAAW,CAAE;YAC7B,SAAS,EAAE,CAAE,0BAAW,CAAE;SAC7B,CAAC;;iBAAA;IAEF,gBAAC;AAAD,CAAC,AADD,IACC;AADY,iBAAS,YACrB,CAAA"} \ No newline at end of file diff --git a/examples/master-player/src/app.module.ts b/examples/master-player/src/app.module.ts index 70a0f237..6e2f329a 100644 --- a/examples/master-player/src/app.module.ts +++ b/examples/master-player/src/app.module.ts @@ -1,17 +1,19 @@ -import {NgModule} from '@angular/core'; -import {BrowserModule} from '@angular/platform-browser'; -import {VgCore} from 'videogular2/core'; -import {VgControlsModule} from 'videogular2/controls'; -import {MasterMedia} from './master-media'; +import { NgModule } from "@angular/core"; +import { BrowserModule } from "@angular/platform-browser"; +import { VgCore } from "videogular2/core"; +import { VgControlsModule } from "videogular2/controls"; +import { MasterMedia } from "./master-media"; +import { VgBufferingModule } from "videogular2/buffering"; @NgModule({ imports: [ BrowserModule, VgCore, - VgControlsModule + VgControlsModule, + VgBufferingModule ], - declarations: [MasterMedia], - bootstrap: [MasterMedia] + declarations: [ MasterMedia ], + bootstrap: [ MasterMedia ] }) export class AppModule { } diff --git a/examples/master-player/src/master-media.html b/examples/master-player/src/master-media.html index f8e08b01..df81de7a 100644 --- a/examples/master-player/src/master-media.html +++ b/examples/master-player/src/master-media.html @@ -1,4 +1,6 @@ + + @@ -18,15 +20,11 @@ - diff --git a/examples/master-player/src/master-media.js b/examples/master-player/src/master-media.js index 4a139c29..d3a1aa74 100644 --- a/examples/master-player/src/master-media.js +++ b/examples/master-player/src/master-media.js @@ -11,7 +11,17 @@ var __metadata = (this && this.__metadata) || function (k, v) { var core_1 = require("@angular/core"); var MasterMedia = (function () { function MasterMedia() { - this.sources = [ + this.master = [ + { + src: "http://static.videogular.com/assets/videos/big_buck_bunny_720p_h264.mov", + type: "video/mp4" + }, + { + src: "http://static.videogular.com/assets/videos/big_buck_bunny_720p_stereo.ogg", + type: "video/ogg" + } + ]; + this.slave = [ { src: "http://static.videogular.com/assets/videos/videogular.mp4", type: "video/mp4" diff --git a/examples/master-player/src/master-media.js.map b/examples/master-player/src/master-media.js.map index a16957e3..91954425 100644 --- a/examples/master-player/src/master-media.js.map +++ b/examples/master-player/src/master-media.js.map @@ -1 +1 @@ -{"version":3,"file":"master-media.js","sourceRoot":"","sources":["master-media.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,qBAAwB,eAAe,CAAC,CAAA;AAMxC;IAGI;QACI,IAAI,CAAC,OAAO,GAAG;YACX;gBACI,GAAG,EAAE,2DAA2D;gBAChE,IAAI,EAAE,WAAW;aACpB;YACD;gBACI,GAAG,EAAE,2DAA2D;gBAChE,IAAI,EAAE,WAAW;aACpB;YACD;gBACI,GAAG,EAAE,4DAA4D;gBACjE,IAAI,EAAE,YAAY;aACrB;SACJ,CAAC;IACN,CAAC;IAtBL;QAAC,gBAAS,CAAC;YACP,QAAQ,EAAE,cAAc;YACxB,WAAW,EAAE,uBAAuB;SACvC,CAAC;;mBAAA;IAoBF,kBAAC;AAAD,CAAC,AAnBD,IAmBC;AAnBY,mBAAW,cAmBvB,CAAA"} \ No newline at end of file +{"version":3,"file":"master-media.js","sourceRoot":"","sources":["master-media.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,qBAAwB,eAAe,CAAC,CAAA;AAMxC;IAII;QACI,IAAI,CAAC,MAAM,GAAG;YACV;gBACI,GAAG,EAAE,yEAAyE;gBAC9E,IAAI,EAAE,WAAW;aACpB;YACD;gBACI,GAAG,EAAE,2EAA2E;gBAChF,IAAI,EAAE,WAAW;aACpB;SACJ,CAAC;QAEF,IAAI,CAAC,KAAK,GAAG;YACT;gBACI,GAAG,EAAE,2DAA2D;gBAChE,IAAI,EAAE,WAAW;aACpB;YACD;gBACI,GAAG,EAAE,2DAA2D;gBAChE,IAAI,EAAE,WAAW;aACpB;YACD;gBACI,GAAG,EAAE,4DAA4D;gBACjE,IAAI,EAAE,YAAY;aACrB;SACJ,CAAC;IACN,CAAC;IAlCL;QAAC,gBAAS,CAAC;YACP,QAAQ,EAAE,cAAc;YACxB,WAAW,EAAE,uBAAuB;SACvC,CAAC;;mBAAA;IAgCF,kBAAC;AAAD,CAAC,AA/BD,IA+BC;AA/BY,mBAAW,cA+BvB,CAAA"} \ No newline at end of file diff --git a/examples/master-player/src/master-media.ts b/examples/master-player/src/master-media.ts index c05377f1..58aeb703 100644 --- a/examples/master-player/src/master-media.ts +++ b/examples/master-player/src/master-media.ts @@ -5,10 +5,22 @@ import {Component} from "@angular/core"; templateUrl: 'src/master-media.html' }) export class MasterMedia { - sources:Array; + master:Array; + slave:Array; constructor() { - this.sources = [ + this.master = [ + { + src: "http://static.videogular.com/assets/videos/big_buck_bunny_720p_h264.mov", + type: "video/mp4" + }, + { + src: "http://static.videogular.com/assets/videos/big_buck_bunny_720p_stereo.ogg", + type: "video/ogg" + } + ]; + + this.slave = [ { src: "http://static.videogular.com/assets/videos/videogular.mp4", type: "video/mp4" diff --git a/src/vg-buffering/vg-buffering.d.ts b/src/vg-buffering/vg-buffering.d.ts index d5f305ea..580f9f56 100644 --- a/src/vg-buffering/vg-buffering.d.ts +++ b/src/vg-buffering/vg-buffering.d.ts @@ -7,16 +7,14 @@ export declare class VgBuffering { vgFor: string; target: IPlayable; checkBufferInterval: number; - displayState: string; - constructor(ref: ElementRef, API: VgAPI); - onPlayerReady(): void; checkInterval: number; currentPlayPos: number; lastPlayPos: number; bufferingDetected: boolean; - bufferCheck(): void; - startBufferCheck(): void; - stopBufferCheck(): void; + displayState: string; + constructor(ref: ElementRef, API: VgAPI); + onPlayerReady(): void; + onUpdateBuffer(isBuffering: any): void; show(): void; hide(): void; } diff --git a/src/vg-buffering/vg-buffering.spec.ts b/src/vg-buffering/vg-buffering.spec.ts index 0c6e03ba..987b5a30 100644 --- a/src/vg-buffering/vg-buffering.spec.ts +++ b/src/vg-buffering/vg-buffering.spec.ts @@ -22,45 +22,26 @@ describe('Volume control', () => { }); describe('onPlayerReady', ()=>{ - it('should subscribe to play and pause media events', ()=>{ + it('should subscribe to bufferDetected media events', ()=>{ spyOn(api, 'getMediaById').and.returnValue({ subscriptions: { - play: {subscribe: jasmine.createSpy('play') }, - pause: {subscribe: jasmine.createSpy('pause') } + bufferDetected: {subscribe: jasmine.createSpy('bufferDetected') } } }); vgBuffering.onPlayerReady(); - expect(vgBuffering.target.subscriptions.play.subscribe).toHaveBeenCalled(); - expect(vgBuffering.target.subscriptions.pause.subscribe).toHaveBeenCalled(); + expect(vgBuffering.target.subscriptions.bufferDetected.subscribe).toHaveBeenCalled(); }); }); - - describe('startBufferCheck', ()=>{ - it('should register bufferCheck in a setInterval', () => { - spyOn(window, 'setInterval').and.returnValue(100); - vgBuffering.checkInterval = 77; - vgBuffering.startBufferCheck(); - expect(window.setInterval).toHaveBeenCalledWith( - vgBuffering.bufferCheck, - 77 - ); - expect(vgBuffering.checkBufferInterval).toBe(100); - }); - }); - - describe('stopBufferCheck', ()=>{ - it('should unregister bufferCheck from setInterval', () => { - spyOn(window, 'clearInterval'); - vgBuffering.stopBufferCheck(); - expect(window.clearInterval).toHaveBeenCalledWith( - vgBuffering.checkBufferInterval - ); + + describe('isBuffering', ()=>{ + it('should show if buffer is detected', () => { + spyOn(vgBuffering, 'show'); + vgBuffering.onUpdateBuffer(true); + expect(vgBuffering.show).toHaveBeenCalled(); }); - it('should set props to hide buffering indicator', () => { - vgBuffering.bufferingDetected = true; + it('should hide if buffer is not detected', () => { spyOn(vgBuffering, 'hide'); - vgBuffering.stopBufferCheck(); - expect(vgBuffering.bufferingDetected).toBe(false); + vgBuffering.onUpdateBuffer(false); expect(vgBuffering.hide).toHaveBeenCalled(); }); }); @@ -80,31 +61,4 @@ describe('Volume control', () => { expect(vgBuffering.displayState).toBe('none'); }); }); - - describe('bufferCheck', ()=>{ - beforeEach(()=>{ - vgBuffering.target = {currentTime:0}; - }); - it('should set bufferingDetected to true', () => { - spyOn(vgBuffering, 'show'); - vgBuffering.bufferingDetected = false; - vgBuffering.target.currentTime = 10; - vgBuffering.lastPlayPos = 10; - vgBuffering.bufferCheck(); - expect(vgBuffering.bufferingDetected).toBe(true); - expect(vgBuffering.lastPlayPos).toBe(10); - expect(vgBuffering.show).toHaveBeenCalled(); - }); - - it('should set bufferingDetected to false', () => { - spyOn(vgBuffering, 'hide'); - vgBuffering.bufferingDetected = true; - vgBuffering.target.currentTime = 20; - vgBuffering.lastPlayPos = 10; - vgBuffering.bufferCheck(); - expect(vgBuffering.bufferingDetected).toBe(false); - expect(vgBuffering.lastPlayPos).toBe(20); - expect(vgBuffering.hide).toHaveBeenCalled(); - }); - }); }); \ No newline at end of file diff --git a/src/vg-buffering/vg-buffering.ts b/src/vg-buffering/vg-buffering.ts index 318bed00..8a73efbf 100644 --- a/src/vg-buffering/vg-buffering.ts +++ b/src/vg-buffering/vg-buffering.ts @@ -103,51 +103,34 @@ export class VgBuffering { vgFor: string; target: IPlayable; checkBufferInterval: number; + checkInterval: number = 50; + currentPlayPos: number = 0; + lastPlayPos: number = 0; + bufferingDetected: boolean; @HostBinding('style.display') displayState: string = 'none'; constructor(ref:ElementRef, public API: VgAPI) { this.elem = ref.nativeElement; API.playerReadyEvent.subscribe((api:VgAPI) => this.onPlayerReady()); - this.bufferCheck = this.bufferCheck.bind(this); } onPlayerReady() { this.vgFor = this.elem.getAttribute('vg-for'); this.target = this.API.getMediaById(this.vgFor); - this.target.subscriptions.play.subscribe(this.startBufferCheck.bind(this)); - this.target.subscriptions.pause.subscribe(this.stopBufferCheck.bind(this)); + this.target.subscriptions.bufferDetected.subscribe( + isBuffering => this.onUpdateBuffer(isBuffering) + ); } - checkInterval: number = 50; - currentPlayPos: number = 0; - lastPlayPos: number = 0; - bufferingDetected: boolean; - - // http://stackoverflow.com/a/23828241/779529 - bufferCheck() { - this.currentPlayPos = this.target.currentTime - const offset = 1 / this.checkInterval - if (!this.bufferingDetected && this.currentPlayPos < (this.lastPlayPos + offset)) { - this.bufferingDetected = true; + onUpdateBuffer(isBuffering) { + if (isBuffering) { this.show(); } - if (this.bufferingDetected && this.currentPlayPos > (this.lastPlayPos + offset)) { - this.bufferingDetected = false; + else { this.hide(); } - this.lastPlayPos = this.currentPlayPos - } - - startBufferCheck() { - this.checkBufferInterval = window.setInterval( this.bufferCheck, this.checkInterval); - } - - stopBufferCheck() { - window.clearInterval(this.checkBufferInterval); - this.bufferingDetected = false; - this.hide(); } show() { diff --git a/src/vg-controls/vg-fullscreen/vg-fullscreen.d.ts b/src/vg-controls/vg-fullscreen/vg-fullscreen.d.ts index 89f0a526..6d7d781d 100644 --- a/src/vg-controls/vg-fullscreen/vg-fullscreen.d.ts +++ b/src/vg-controls/vg-fullscreen/vg-fullscreen.d.ts @@ -1,14 +1,14 @@ import { ElementRef } from '@angular/core'; import { VgAPI } from '../../services/vg-api'; -import { VgFullscreenAPI } from "../../services/vg-fullscreen-api"; import { VgAbstractControl } from '../vg-abstract-control'; export declare class VgFullscreen extends VgAbstractControl { API: VgAPI; elem: HTMLElement; vgFor: string; target: Object; - fsAPI: VgFullscreenAPI; + isFullscreen: boolean; constructor(ref: ElementRef, API: VgAPI); + onChangeFullscreen(fsState: boolean): void; onPlayerReady(): void; onClick(): void; } diff --git a/src/vg-media/i-playable.d.ts b/src/vg-media/i-playable.d.ts index a71648c3..8ec9a06c 100644 --- a/src/vg-media/i-playable.d.ts +++ b/src/vg-media/i-playable.d.ts @@ -16,6 +16,7 @@ export interface IPlayable { dispatchEvent?: Function; } export interface IMediaSubscriptions { + bufferDetected: Observable; canPlay: Observable; canPlayThrough: Observable; loadedMetadata: Observable; diff --git a/src/vg-media/i-playable.ts b/src/vg-media/i-playable.ts index 676965a0..02513c44 100644 --- a/src/vg-media/i-playable.ts +++ b/src/vg-media/i-playable.ts @@ -18,6 +18,7 @@ export interface IPlayable { } export interface IMediaSubscriptions { + bufferDetected: Observable; canPlay: Observable; canPlayThrough: Observable; loadedMetadata: Observable; diff --git a/src/vg-media/vg-media.d.ts b/src/vg-media/vg-media.d.ts index 4e44fa77..369fa728 100644 --- a/src/vg-media/vg-media.d.ts +++ b/src/vg-media/vg-media.d.ts @@ -1,6 +1,9 @@ -import { ElementRef, OnInit } from '@angular/core'; +import { ElementRef, OnInit } from "@angular/core"; import { IPlayable, IMediaSubscriptions } from "./i-playable"; +import { VgAPI } from "../services/vg-api"; +import { Observer } from "rxjs"; export declare class VgMedia implements OnInit, IPlayable { + private api; elem: any; private _vgMaster; isMaster: boolean; @@ -10,11 +13,23 @@ export declare class VgMedia implements OnInit, IPlayable { subscriptions: IMediaSubscriptions | any; canPlay: boolean; canPlayThrough: boolean; + isBufferDetected: boolean; isMetadataLoaded: boolean; + isReadyToPlay: boolean; isWaiting: boolean; isCompleted: boolean; - constructor(ref: ElementRef); + checkInterval: number; + currentPlayPos: number; + lastPlayPos: number; + bufferObserver: Observer; + checkBufferSubscription: any; + syncSubscription: any; + canPlayAllSubscription: any; + playAtferSync: boolean; + constructor(ref: ElementRef, api: VgAPI); ngOnInit(): void; + prepareSync(): void; + startSync(): void; onMutation(mutations: any): void; play(): void; pause(): void; @@ -36,4 +51,8 @@ export declare class VgMedia implements OnInit, IPlayable { onProgress(event: any): void; onVolumeChange(event: any): void; onError(event: any): void; + bufferCheck(): void; + startBufferCheck(): void; + stopBufferCheck(): void; + onBufferDetected(): void; } diff --git a/src/vg-media/vg-media.spec.ts b/src/vg-media/vg-media.spec.ts index c54df9a4..4ecf95c5 100644 --- a/src/vg-media/vg-media.spec.ts +++ b/src/vg-media/vg-media.spec.ts @@ -33,7 +33,7 @@ describe('Videogular Media', () => { }; api = new VgAPI(); - media = new VgMedia(ref); + media = new VgMedia(ref, api); }); it('Should load a new media if a change on dom have been happened', () => { diff --git a/src/vg-media/vg-media.ts b/src/vg-media/vg-media.ts index d2e0fafa..8d713266 100644 --- a/src/vg-media/vg-media.ts +++ b/src/vg-media/vg-media.ts @@ -1,37 +1,54 @@ -import {ElementRef, OnInit, Directive, Input} from '@angular/core'; - -import {IPlayable, IMediaSubscriptions} from "./i-playable"; -import {Observable} from "rxjs/Observable"; -import {VgEvents} from "../events/vg-events"; -import {VgStates} from "../states/vg-states"; +import { ElementRef, OnInit, Directive, Input } from "@angular/core"; +import { IPlayable, IMediaSubscriptions } from "./i-playable"; +import { Observable } from "rxjs/Observable"; +import { VgEvents } from "../events/vg-events"; +import { VgStates } from "../states/vg-states"; +import { VgAPI } from "../services/vg-api"; +import { TimerObservable } from "rxjs/observable/TimerObservable"; +import { Subscription, Observer } from "rxjs"; @Directive({ selector: '[vg-media]' }) export class VgMedia implements OnInit, IPlayable { - elem:any; + elem: any; + + private _vgMaster: boolean = false; - private _vgMaster:boolean = false; - @Input('vg-master') set isMaster(value:boolean) { + @Input('vg-master') set isMaster(value: boolean) { this._vgMaster = value; } - get isMaster():boolean { + + get isMaster(): boolean { return this._vgMaster; } - state:string = VgStates.VG_PAUSED; - - time:any = {current: 0, total: 0, left: 0}; - buffer:any = {end: 0}; - subscriptions:IMediaSubscriptions | any; - - canPlay:boolean = false; - canPlayThrough:boolean = false; - isMetadataLoaded:boolean = false; - isWaiting:boolean = false; - isCompleted:boolean = false; - - constructor(ref:ElementRef) { + state: string = VgStates.VG_PAUSED; + + time: any = { current: 0, total: 0, left: 0 }; + buffer: any = { end: 0 }; + subscriptions: IMediaSubscriptions | any; + + canPlay: boolean = false; + canPlayThrough: boolean = false; + isBufferDetected:boolean = false; + isMetadataLoaded: boolean = false; + isReadyToPlay: boolean = false; + isWaiting: boolean = false; + isCompleted: boolean = false; + + + checkInterval: number = 200; + currentPlayPos: number = 0; + lastPlayPos: number = 0; + + bufferObserver:Observer; + checkBufferSubscription:any; + syncSubscription:any; + canPlayAllSubscription:any; + playAtferSync:boolean = false; + + constructor(ref: ElementRef, private api: VgAPI) { this.elem = ref.nativeElement; } @@ -42,6 +59,8 @@ export class VgMedia implements OnInit, IPlayable { loadedMetadata: Observable.fromEvent(this.elem, VgEvents.VG_LOADED_METADATA), waiting: Observable.fromEvent(this.elem, VgEvents.VG_WAITING), progress: Observable.fromEvent(this.elem, VgEvents.VG_PROGRESS), + seeking: Observable.fromEvent(this.elem, VgEvents.VG_SEEKING), + seeked: Observable.fromEvent(this.elem, VgEvents.VG_SEEKED), ended: Observable.fromEvent(this.elem, VgEvents.VG_ENDED), playing: Observable.fromEvent(this.elem, VgEvents.VG_PLAYING), play: Observable.fromEvent(this.elem, VgEvents.VG_PLAY), @@ -53,7 +72,7 @@ export class VgMedia implements OnInit, IPlayable { endAds: Observable.fromEvent(window, VgEvents.VG_END_ADS), // See changes on child elements to reload the video file mutation: Observable.create( - (observer:any) => { + (observer: any) => { let domObs = new MutationObserver((mutations) => { observer.next(mutations); }); @@ -64,10 +83,18 @@ export class VgMedia implements OnInit, IPlayable { domObs.disconnect(); }; } + ), + bufferDetected: Observable.create( + (observer:any) => { + this.bufferObserver = observer; + + return () => { + observer.disconnect(); + }; + } ) }; - this.subscriptions.mutation.subscribe(this.onMutation.bind(this)); this.subscriptions.canPlay.subscribe(this.onCanPlay.bind(this)); this.subscriptions.canPlayThrough.subscribe(this.onCanPlayThrough.bind(this)); @@ -81,9 +108,63 @@ export class VgMedia implements OnInit, IPlayable { this.subscriptions.timeUpdate.subscribe(this.onTimeUpdate.bind(this)); this.subscriptions.volumeChange.subscribe(this.onVolumeChange.bind(this)); this.subscriptions.error.subscribe(this.onError.bind(this)); + + if (this.isMaster) { + this.api.playerReadyEvent.subscribe( + () => { + this.prepareSync(); + } + ); + } } - onMutation(mutations:any) { + prepareSync() { + let canPlayAll: Array> = []; + + for (var media in this.api.medias) { + canPlayAll.push(this.api.medias[ media ].subscriptions.canPlay); + } + + this.canPlayAllSubscription = Observable.combineLatest(canPlayAll, + (...params) => { + var allReady: boolean = params.some(event => event.target.readyState === 4); + + if (allReady && !this.syncSubscription) { + this.startSync(); + this.syncSubscription.unsubscribe(); + } + } + ).subscribe(); + } + + startSync() { + this.syncSubscription = TimerObservable.create(0, 1000).subscribe( + () => { + for (var media in this.api.medias) { + if (this.api.medias[ media ] != this) { + var diff: number = this.api.medias[ media ].currentTime - this.currentTime; + + if (diff < -0.3 || diff > 0.3) { + this.playAtferSync = (this.state === VgStates.VG_PLAYING); + + this.pause(); + this.api.medias[ media ].pause(); + this.api.medias[ media ].currentTime = this.currentTime; + } + else { + if (this.playAtferSync) { + this.play(); + this.api.medias[ media ].play(); + this.playAtferSync = false; + } + } + } + } + } + ); + } + + onMutation(mutations: any) { this.elem.pause(); this.elem.currentTime = 0; @@ -94,7 +175,7 @@ export class VgMedia implements OnInit, IPlayable { play() { this.elem.play(); } - + pause() { this.elem.pause(); } @@ -108,8 +189,8 @@ export class VgMedia implements OnInit, IPlayable { } set currentTime(seconds) { - this.elem.currentTime = seconds; - this.elem.dispatchEvent(new CustomEvent(VgEvents.VG_SEEK)); + this.elem.currentTime = seconds; + //this.elem.dispatchEvent(new CustomEvent(VgEvents.VG_SEEK)); } get currentTime() { @@ -136,42 +217,62 @@ export class VgMedia implements OnInit, IPlayable { return this.elem.buffered; } - onCanPlay(event:any) { + onCanPlay(event: any) { this.canPlay = true; } - onCanPlayThrough(event:any) { + onCanPlayThrough(event: any) { this.canPlayThrough = true; } - onLoadMetadata(event:any) { + onLoadMetadata(event: any) { this.isMetadataLoaded = true; this.time.total = this.duration * 1000; } - onWait(event:any) { + onWait(event: any) { this.isWaiting = true; } - onComplete(event:any) { + onComplete(event: any) { this.isCompleted = true; this.state = VgStates.VG_ENDED; } - onStartPlaying(event:any) { + onStartPlaying(event: any) { this.state = VgStates.VG_PLAYING; } - onPlay(event:any) { + onPlay(event: any) { this.state = VgStates.VG_PLAYING; + + if (this.isMaster) { + if (!this.syncSubscription || this.syncSubscription.isUnsubscribed) { + this.startSync(); + } + } + + if (this.bufferObserver) { + this.startBufferCheck(); + } } - onPause(event:any) { + onPause(event: any) { this.state = VgStates.VG_PAUSED; + + if (this.isMaster) { + if (!this.playAtferSync) { + this.syncSubscription.unsubscribe(); + } + } + + if (this.bufferObserver) { + this.stopBufferCheck(); + } } - onTimeUpdate(event:any) { + onTimeUpdate(event: any) { var end = this.buffered.length - 1; this.time.current = this.currentTime * 1000; @@ -182,7 +283,7 @@ export class VgMedia implements OnInit, IPlayable { } } - onProgress(event:any) { + onProgress(event: any) { var end = this.buffered.length - 1; if (end >= 0) { @@ -190,11 +291,47 @@ export class VgMedia implements OnInit, IPlayable { } } - onVolumeChange(event:any) { + onVolumeChange(event: any) { // TODO: Save to localstorage the current volume } - onError(event:any) { + onError(event: any) { // TODO: Handle error messages } + + // http://stackoverflow.com/a/23828241/779529 + bufferCheck() { + const offset = 1 / this.checkInterval; + this.currentPlayPos = this.currentTime; + + if (!this.isBufferDetected && this.currentPlayPos < (this.lastPlayPos + offset)) { + this.isBufferDetected = true; + } + + if (this.isBufferDetected && this.currentPlayPos > (this.lastPlayPos + offset)) { + this.isBufferDetected = false; + } + + this.bufferObserver.next(this.isBufferDetected); + + this.lastPlayPos = this.currentPlayPos; + } + + startBufferCheck() { + this.checkBufferSubscription = TimerObservable.create(0, this.checkInterval).subscribe( + () => { + this.bufferCheck(); + } + ); + } + + stopBufferCheck() { + this.checkBufferSubscription.unsubscribe(); + this.isBufferDetected = false; + this.bufferObserver.next(this.isBufferDetected); + } + + onBufferDetected() { + + } } diff --git a/src/vg-player/vg-player.d.ts b/src/vg-player/vg-player.d.ts index 2cb73cb1..d30d8d02 100644 --- a/src/vg-player/vg-player.d.ts +++ b/src/vg-player/vg-player.d.ts @@ -11,5 +11,5 @@ export declare class VgPlayer implements AfterContentInit { medias: QueryList; constructor(ref: ElementRef, api: VgAPI); ngAfterContentInit(): void; - onChangeFullscreen(fsState: any): void; + onChangeFullscreen(fsState: boolean): void; }