2018-09-02 19:22:28 +00:00
"use strict" ;
2018-12-02 15:07:43 +00:00
var Uint64 = require ( "../wType/uint64" ) ;
var Address = require ( "../wType/address" ) ;
2018-09-02 19:22:28 +00:00
var Controller = require ( "./controller" ) ;
var Button = require ( "./button" ) ;
2018-12-02 15:07:43 +00:00
var ImageById = require ( "./imageById" ) ;
2018-09-02 19:22:28 +00:00
var Vocabulary = require ( "./vocabulary" ) ;
2018-12-17 17:15:58 +00:00
var Audio = require ( "./file/audio" ) ;
2019-01-24 19:54:16 +00:00
var Model = require ( "./localModel" ) ;
2018-12-17 17:15:58 +00:00
2018-09-02 19:22:28 +00:00
var Enum = require ( "../utils/enum" ) ;
2018-12-17 17:15:58 +00:00
var StateMachine = require ( "../utils/stateMachine" ) ;
2018-09-02 19:22:28 +00:00
var Player = Controller . inherit ( {
className : "Player" ,
constructor : function ( addr ) {
Controller . fn . constructor . call ( this , addr ) ;
this . controls = Object . create ( null ) ;
this . views = Object . create ( null ) ;
2018-12-02 15:07:43 +00:00
this . mode = PlayerMode . straight . playback ;
2019-01-24 19:54:16 +00:00
this . progress = new ProgressModel ( ) ;
2018-12-17 17:15:58 +00:00
this . _audio = null ;
this . _createStateMachine ( ) ;
2019-01-03 00:26:42 +00:00
this . _createPlayingInfrastructure ( ) ;
2018-09-02 19:22:28 +00:00
this . addHandler ( "get" ) ;
this . addHandler ( "viewsChange" ) ;
2018-12-17 17:15:58 +00:00
this . addHandler ( "play" ) ;
this . addHandler ( "pause" ) ;
2019-01-24 19:54:16 +00:00
this . _playbackInterval = setInterval ( this . _onInterval . bind ( this ) , 250 ) ;
2018-12-17 17:15:58 +00:00
} ,
destructor : function ( ) {
2019-01-24 19:54:16 +00:00
this . _clearInterval ( this . _playbackInterval ) ;
this . _destroyPlayingInfrastructure ( ) ;
2018-12-17 17:15:58 +00:00
this . _fsm . destructor ( ) ;
2019-01-24 19:54:16 +00:00
this . progress . destructor ( ) ;
2018-12-17 17:15:58 +00:00
Controller . fn . destructor . call ( this ) ;
2018-09-02 19:22:28 +00:00
} ,
_addControl : function ( type , address ) {
var t = type . valueOf ( ) ;
if ( this . controls [ t ] !== undefined ) {
throw new Error ( "An attempt to add multiple instances of " + ItemType . reversed [ t ] + " into Player" ) ;
}
if ( ItemType . reversed [ t ] !== undefined ) {
switch ( t ) {
case ItemType . straight . playPause :
2019-01-03 00:26:42 +00:00
case ItemType . straight . prev :
case ItemType . straight . next :
2018-09-02 19:22:28 +00:00
var btn = new Button ( address . clone ( ) ) ;
btn . itemType = t ;
this . controls [ t ] = btn ;
this . addController ( btn ) ;
2018-10-28 21:32:44 +00:00
this . trigger ( "newElement" , btn , t ) ;
2018-09-02 19:22:28 +00:00
break ;
default :
2019-01-03 00:26:42 +00:00
this . trigger ( "serviceMessage" , "An attempt to add ItemType " + ItemType . reversed [ t ] + " to controls of the Player, but it's not qualified to be a control" , 1 ) ;
2018-09-02 19:22:28 +00:00
}
} else {
2019-01-03 00:26:42 +00:00
this . trigger ( "serviceMessage" , "An unrecgnized item ItemType in Player: " + t , 1 ) ;
2018-09-02 19:22:28 +00:00
}
} ,
_addView : function ( type , address ) {
var t = type . valueOf ( ) ;
2018-12-02 15:07:43 +00:00
var ctrl ;
var supported = false ;
2018-09-02 19:22:28 +00:00
if ( this . views [ t ] !== undefined ) {
throw new Error ( "An attempt to add multiple instances of " + ItemType . reversed [ t ] + " into Player" ) ;
}
if ( ItemType . reversed [ t ] !== undefined ) {
switch ( t ) {
case ItemType . straight . queue :
2019-01-03 00:26:42 +00:00
this . trigger ( "serviceMessage" , "Queue is not supported yet in Player" , 1 ) ;
2018-09-02 19:22:28 +00:00
break ;
case ItemType . straight . currentPlayback :
2018-12-02 15:07:43 +00:00
ctrl = new Vocabulary ( address . clone ( ) ) ;
ctrl . on ( "newElement" , this . _onNewPlayBackElement , this ) ;
ctrl . on ( "removeElement" , this . _onNewRemoveBackElement , this ) ;
supported = true ;
break ;
case ItemType . straight . picture :
ctrl = new ImageById ( null , address . back ( ) ) ;
ctrl . ItemType = t ;
this . views [ t ] = ctrl ;
this . trigger ( "newElement" , ctrl , t ) ;
supported = false ; //just to avoid adding with addController, since ImageById is not a controller
2018-10-28 21:32:44 +00:00
break ;
2018-09-02 19:22:28 +00:00
default :
2019-01-03 00:26:42 +00:00
this . trigger ( "serviceMessage" , "An attempt to add ItemType " + ItemType . reversed [ t ] + " to views of the Player, but it's not qualified to be a view" , 1 ) ;
2018-09-02 19:22:28 +00:00
}
} else {
2019-01-03 00:26:42 +00:00
this . trigger ( "serviceMessage" , "An unrecognized item ItemType in Player: " + t , 1 ) ;
2018-09-02 19:22:28 +00:00
}
2018-12-02 15:07:43 +00:00
if ( supported ) {
ctrl . ItemType = t ;
this . views [ t ] = ctrl ;
this . addController ( ctrl ) ;
this . trigger ( "newElement" , ctrl , t ) ;
}
2018-09-02 19:22:28 +00:00
} ,
2019-01-24 19:54:16 +00:00
_createPlayingInfrastructure : function ( ) {
this . _ctx = new AudioContext ( ) ;
this . _decoder = new Mp3Decoder ( ) ;
this . _currentTime = 0 ;
this . _ctx . suspend ( ) ;
} ,
_destroyPlayingInfrastructure : function ( ) {
this . _ctx . close ( ) ;
this . _decoder . delete ( ) ;
2019-01-03 00:26:42 +00:00
} ,
2018-12-17 17:15:58 +00:00
_createStateMachine : function ( ) {
this . _fsm = new StateMachine ( "initial" , graphs [ this . mode ] ) ;
this . _fsm . on ( "stateChanged" , this . _onStateChanged , this ) ;
} ,
2018-09-02 19:22:28 +00:00
_h _get : function ( ev ) {
var data = ev . getData ( ) ;
var controls = data . at ( "controls" ) ;
var views = data . at ( "views" ) ;
2018-12-02 15:07:43 +00:00
var mode = data . at ( "mode" ) . valueOf ( ) ;
2018-09-02 19:22:28 +00:00
var size , i , vc ;
size = controls . length ( ) ;
for ( i = 0 ; i < size ; ++ i ) {
vc = controls . at ( i ) ;
this . _addControl ( vc . at ( "type" ) , vc . at ( "address" ) ) ;
}
size = views . length ( ) ;
for ( i = 0 ; i < size ; ++ i ) {
vc = views . at ( i ) ;
2018-10-28 21:32:44 +00:00
this . _addView ( vc . at ( "type" ) , vc . at ( "address" ) ) ;
2018-09-02 19:22:28 +00:00
}
2018-10-28 21:32:44 +00:00
2018-12-02 15:07:43 +00:00
if ( this . mode !== mode ) {
if ( PlayerMode . reversed [ mode ] === undefined ) {
throw new Error ( "Unsupported mode of player: " + mode ) ;
}
this . mode = mode ;
}
2018-10-28 21:32:44 +00:00
this . initialized = true ;
this . trigger ( "data" ) ;
2018-09-02 19:22:28 +00:00
} ,
2018-12-17 17:15:58 +00:00
_h _pause : function ( ev ) {
2018-12-21 21:21:12 +00:00
this . _fsm . manipulation ( "pause" ) ;
2018-12-17 17:15:58 +00:00
} ,
_h _play : function ( ev ) {
this . _fsm . manipulation ( "play" ) ;
} ,
2018-09-02 19:22:28 +00:00
_h _viewsChange : function ( ev ) {
var data = ev . getData ( ) ;
var add = data . at ( "add" ) ;
var remove = data . at ( "remove" ) ;
var size , i , vc ;
size = remove . length ( ) ;
for ( i = 0 ; i < size ; ++ i ) {
2019-01-03 00:26:42 +00:00
this . _removeView ( remove . at ( i ) . valueOf ( ) ) ;
2018-09-02 19:22:28 +00:00
}
size = add . length ( ) ;
for ( i = 0 ; i < size ; ++ i ) {
vc = add . at ( i ) ;
2018-10-28 21:32:44 +00:00
this . _addView ( vc . at ( "type" ) , vc . at ( "address" ) ) ;
2018-09-02 19:22:28 +00:00
}
} ,
2019-01-24 19:54:16 +00:00
_onAudioNewSlice : function ( frames ) {
var arr = new Uint8Array ( frames . valueOf ( ) ) ;
this . _decoder . addFragment ( arr ) ;
while ( this . _decoder . hasMore ( ) ) {
var sb = this . _decoder . decode ( 9999 ) ;
if ( sb === undefined ) {
break ;
} else {
var src = this . _ctx . createBufferSource ( ) ;
src . buffer = sb ;
src . connect ( this . _ctx . destination ) ;
src . start ( this . _currentTime ) ;
this . _currentTime += sb . duration ;
}
}
this . progress . setLoad ( this . _currentTime / this . _audio . getDuration ( ) ) ;
2018-12-21 21:21:12 +00:00
2018-12-17 17:15:58 +00:00
this . _fsm . manipulation ( "newFrames" ) ;
if ( this . _audio . hasMore ( ) ) {
2019-01-24 19:54:16 +00:00
this . _audio . requestSlice ( audioPortion ) ;
2018-12-17 17:15:58 +00:00
} else {
this . _fsm . manipulation ( "noMoreFrames" ) ;
}
} ,
2019-01-03 00:26:42 +00:00
_onControllerReady : function ( ) {
this . _fsm . manipulation ( "controllerReady" ) ;
} ,
2019-01-24 19:54:16 +00:00
_onInterval : function ( ) {
if ( this . _audio && this . _audio . initialized ) {
this . progress . setPlayback ( this . _ctx . currentTime / this . _audio . getDuration ( ) ) ;
}
} ,
2018-12-02 15:07:43 +00:00
_onNewPlayBackElement : function ( key , element ) {
switch ( key ) {
case "image" :
var address = new Address ( [ "images" , element . toString ( ) ] ) ;
this . _addView ( new Uint64 ( ItemType . straight . picture ) , address ) ;
address . destructor ( ) ;
break ;
2018-12-17 17:15:58 +00:00
case "audio" :
if ( this . mode === PlayerMode . straight . playback ) {
this . _audio = new Audio ( new Address ( [ "music" , element . toString ( ) ] ) ) ;
this . addForeignController ( "Corax" , this . _audio ) ;
2019-01-24 19:54:16 +00:00
this . _audio . on ( "slice" , this . _onAudioNewSlice , this ) ;
2019-01-03 00:26:42 +00:00
this . _audio . on ( "ready" , this . _onControllerReady , this ) ;
2018-12-17 17:15:58 +00:00
this . _fsm . manipulation ( "controller" ) ;
}
break ;
2018-12-02 15:07:43 +00:00
}
} ,
_onNewRemoveBackElement : function ( key ) {
switch ( key ) {
case "image" :
2019-01-03 00:26:42 +00:00
this . _removeView ( ItemType . straight . picture ) ;
2018-12-02 15:07:43 +00:00
break ;
2018-12-17 17:15:58 +00:00
case "audio" :
this . removeForeignController ( this . _audio ) ;
this . _audio . destructor ( ) ;
this . _audio = null ;
}
} ,
_onStateChanged : function ( e ) {
switch ( e . newState ) {
case "initial" :
2019-01-03 00:26:42 +00:00
if ( e . manipulation === "noController" ) {
this . removeForeignController ( this . _audio ) ;
this . _audio . destructor ( ) ;
this . _audio = null ;
2019-01-24 19:54:16 +00:00
this . _destroyPlayingInfrastructure ( ) ;
2019-01-03 00:26:42 +00:00
this . _createPlayingInfrastructure ( ) ;
}
2018-12-17 17:15:58 +00:00
break ;
case "initialPlaying" :
2019-01-03 00:26:42 +00:00
if ( e . manipulation === "noController" ) {
2019-01-24 19:54:16 +00:00
this . _ctx . suspend ( ) ;
2019-01-03 00:26:42 +00:00
this . removeForeignController ( this . _audio ) ;
this . audio . destructor ( ) ;
this . _audio = null ;
2019-01-24 19:54:16 +00:00
this . _destroyPlayingInfrastructure ( ) ;
2019-01-03 00:26:42 +00:00
this . _createPlayingInfrastructure ( ) ;
}
2018-12-17 17:15:58 +00:00
break ;
2019-01-03 00:26:42 +00:00
case "controllerNotReady" :
break
case "controllerNotReadyPlaying" :
break
2018-12-17 17:15:58 +00:00
case "hasController" :
break ;
case "hasControllerPlaying" :
if ( this . _audio . hasMore ( ) ) {
2019-01-24 19:54:16 +00:00
this . _audio . requestSlice ( audioPortion ) ;
2019-01-03 00:26:42 +00:00
2019-01-24 19:54:16 +00:00
this . _ctx . resume ( ) ; //todo temporal
2018-12-17 17:15:58 +00:00
} else {
this . _fsm . manipulation ( "noMoreFrames" ) ;
}
break ;
case "paused" :
switch ( e . oldState ) {
case "playing" :
2019-01-24 19:54:16 +00:00
this . _ctx . suspend ( ) ;
2018-12-17 17:15:58 +00:00
break ;
}
break ;
case "pausedAllLoaded" :
switch ( e . oldState ) {
case "playingAllLoaded" :
2019-01-24 19:54:16 +00:00
this . _ctx . suspend ( ) ;
2018-12-17 17:15:58 +00:00
break ;
}
break ;
case "playing" :
2019-01-24 19:54:16 +00:00
this . _ctx . resume ( ) ;
2018-12-17 17:15:58 +00:00
break ;
case "playingAllLoaded" :
switch ( e . oldState ) {
case "pausedAllLoaded" :
2019-01-24 19:54:16 +00:00
this . _ctx . resume ( ) ;
2018-12-17 17:15:58 +00:00
break ;
}
break ;
2018-12-02 15:07:43 +00:00
}
} ,
2018-09-02 19:22:28 +00:00
_removeControl : function ( type ) {
2019-01-03 00:26:42 +00:00
var ctrl = this . controls [ type ] ;
if ( ctrl !== undefined ) {
this . trigger ( "removeElement" , type ) ;
this . removeController ( ctrl ) ;
ctrl . destructor ( ) ;
}
2018-09-02 19:22:28 +00:00
} ,
_removeView : function ( type ) {
2019-01-03 00:26:42 +00:00
var view = this . views [ type ] ;
if ( view !== undefined ) {
this . trigger ( "removeElement" , type ) ;
if ( type !== ItemType . straight . picture ) {
this . removeController ( view ) ;
}
if ( type === ItemType . straight . currentPlayback ) {
if ( this . views [ ItemType . straight . picture ] ) {
this . _removeView ( ItemType . straight . picture ) ;
}
this . _fsm . manipulation ( "noController" ) ;
}
delete this . views [ type ] ;
view . destructor ( ) ;
}
2018-09-02 19:22:28 +00:00
}
} ) ;
var ItemType = new Enum ( "ItemType" ) ;
ItemType . add ( "playPause" ) ;
ItemType . add ( "currentPlayback" ) ;
ItemType . add ( "queue" ) ;
2018-12-02 15:07:43 +00:00
ItemType . add ( "picture" ) ;
2019-01-03 00:26:42 +00:00
ItemType . add ( "prev" ) ;
ItemType . add ( "next" ) ;
2018-12-02 15:07:43 +00:00
var PlayerMode = new Enum ( "PlayerMode" ) ;
PlayerMode . add ( "playback" ) ;
2018-09-02 19:22:28 +00:00
Player . ItemType = ItemType ;
2018-12-17 17:15:58 +00:00
var graphs = Object . create ( null ) ;
graphs [ PlayerMode . straight . playback ] = {
"initial" : {
2019-01-03 00:26:42 +00:00
controller : "controllerNotReady" ,
2018-12-17 17:15:58 +00:00
play : "initialPlaying"
} ,
"initialPlaying" : {
pause : "initial" ,
2019-01-03 00:26:42 +00:00
controller : "controllerNotReadyPlaying"
} ,
"controllerNotReady" : {
play : "controllerNotReadyPlaying" ,
controllerReady : "hasController"
} ,
"controllerNotReadyPlaying" : {
pause : "controllerNotReady" ,
controllerReady : "hasControllerPlaying"
2018-12-17 17:15:58 +00:00
} ,
"hasController" : {
newFrames : "paused" ,
2019-01-03 00:26:42 +00:00
play : "hasControllerPlaying" ,
noController : "initial"
2018-12-17 17:15:58 +00:00
} ,
"hasControllerPlaying" : {
newFrames : "playing" ,
2019-01-03 00:26:42 +00:00
pause : "hasController" ,
noController : "initialPlaying"
2018-12-17 17:15:58 +00:00
} ,
"paused" : {
play : "playing" ,
2019-01-03 00:26:42 +00:00
noController : "initial" ,
2018-12-17 17:15:58 +00:00
noMoreFrames : "pausedAllLoaded"
} ,
"pausedAllLoaded" : {
2019-01-03 00:26:42 +00:00
play : "playingAllLoaded" ,
noController : "initial"
2018-12-17 17:15:58 +00:00
} ,
"playing" : {
2018-12-21 21:21:12 +00:00
pause : "paused" ,
2019-01-03 00:26:42 +00:00
noMoreFrames : "playingAllLoaded" ,
noController : "initialPlaying"
2018-12-17 17:15:58 +00:00
} ,
"playingAllLoaded" : {
2019-01-03 00:26:42 +00:00
pause : "pausedAllLoaded" ,
noController : "initialPlaying"
2018-12-17 17:15:58 +00:00
}
}
2019-01-24 19:54:16 +00:00
var audioPortion = 1024 * 50 ;
var ProgressModel = Model . inherit ( {
className : "ProgressModel" ,
constructor : function ( properties ) {
Model . fn . constructor . call ( this , properties ) ;
this . load = 0 ;
this . playback = 0 ;
this . initialized = true ;
} ,
setLoad : function ( l ) {
if ( l !== this . load ) {
this . load = l ;
this . trigger ( "load" , l ) ;
}
} ,
setPlayback : function ( p ) {
if ( p !== this . playback ) {
this . playback = p ;
this . trigger ( "playback" , p ) ;
}
}
} ) ;
2018-09-02 19:22:28 +00:00
module . exports = Player ;