2012-10-06 16:36:05 +00:00
( function ( context , $ ) {
2012-12-07 00:28:48 +00:00
"use strict" ;
2012-10-06 16:36:05 +00:00
context . JK = context . JK || { } ;
context . JK . SessionScreen = function ( app ) {
2012-10-14 15:48:56 +00:00
var logger = context . JK . logger ;
2013-03-02 19:02:42 +00:00
var sessionModel = null ;
2012-10-29 15:10:02 +00:00
var sessionId ;
2012-10-22 02:55:05 +00:00
var tracks = { } ;
2013-05-15 05:59:09 +00:00
var myTracks = [ ] ;
2013-01-30 16:50:43 +00:00
var mixers = [ ] ;
2013-05-22 05:03:34 +00:00
var configureTrackDialog ;
2013-05-23 12:46:40 +00:00
var addNewGearDialog ;
2014-01-05 03:47:23 +00:00
var localRecordingsDialog = null ;
2014-01-06 20:35:35 +00:00
var recordingFinishedDialog = null ;
2014-01-13 16:11:26 +00:00
var friendSelectorDialog = null ;
2014-01-14 06:12:00 +00:00
var inviteMusiciansUtil = null ;
2013-05-31 02:07:33 +00:00
var screenActive = false ;
2013-02-03 22:47:17 +00:00
var currentMixerRangeMin = null ;
var currentMixerRangeMax = null ;
2013-02-14 07:02:05 +00:00
var lookingForMixersCount = 0 ;
var lookingForMixersTimer = null ;
var lookingForMixers = { } ;
2013-11-03 20:55:55 +00:00
var $recordingTimer = null ;
var recordingTimerInterval = null ;
var startTimeDate = null ;
var startingRecording = false ; // double-click guard
2014-01-05 03:47:23 +00:00
var claimedRecording = null ;
var playbackControls = null ;
2013-11-03 20:55:55 +00:00
var rest = JK . Rest ( ) ;
2013-02-14 07:02:05 +00:00
2013-09-10 02:07:12 +00:00
var RENDER _SESSION _DELAY = 750 ; // When I need to render a session, I have to wait a bit for the mixers to be there.
2013-02-05 05:10:37 +00:00
var defaultParticipant = {
2013-02-10 00:33:47 +00:00
tracks : [ {
instrument _id : "unknown"
} ] ,
2013-02-05 05:10:37 +00:00
user : {
first _name : 'Unknown' ,
last _name : 'User' ,
photo _url : null
}
} ;
2013-08-03 18:30:45 +00:00
// Be sure to copy/extend these instead of modifying in place
var trackVuOpts = {
vuType : "vertical" ,
lightCount : 13 ,
lightWidth : 3 ,
lightHeight : 17
} ;
// Must add faderId key to this
var trackFaderOpts = {
faderType : "vertical" ,
height : 83
} ;
2013-02-26 03:54:09 +00:00
// Recreate ChannelGroupIDs ENUM from C++
var ChannelGroupIds = {
"MasterGroup" : 0 ,
"MonitorGroup" : 1 ,
"AudioInputMusicGroup" : 2 ,
"AudioInputChatGroup" : 3 ,
"MediaTrackGroup" : 4 ,
"StreamOutMusicGroup" : 5 ,
"StreamOutChatGroup" : 6 ,
"UserMusicInputGroup" : 7 ,
"UserChatInputGroup" : 8 ,
2013-09-11 22:29:32 +00:00
"PeerAudioInputMusicGroup" : 9 ,
2013-09-12 12:02:13 +00:00
"PeerMediaTrackGroup" : 10
2013-02-26 03:54:09 +00:00
} ;
2013-02-05 05:10:37 +00:00
2013-07-09 03:06:01 +00:00
// recreate eThresholdType enum from MixerDialog.h
var alert _type = {
2013-09-06 02:27:43 +00:00
0 : { "title" : "" , "message" : "" } , // NO_EVENT,
1 : { "title" : "" , "message" : "" } , // BACKEND_ERROR: generic error - eg P2P message error
2 : { "title" : "" , "message" : "" } , // BACKEND_MIXER_CHANGE, - event that controls have been regenerated
3 : { "title" : "" , "message" : "" } , // PACKET_JTR,
4 : { "title" : "Packet Loss" , "message" : "Your network connection is currently experiencing packet loss at a rate that is too high to deliver good audio quality. For troubleshooting tips, click here." } , // PACKET_LOSS
5 : { "title" : "" , "message" : "" } , // PACKET_LATE,
6 : { "title" : "" , "message" : "" } , // JTR_QUEUE_DEPTH,
7 : { "title" : "" , "message" : "" } , // NETWORK_JTR,
8 : { "title" : "Session Latency" , "message" : "The latency of your audio device combined with your Internet connection has become high enough to impact your session quality. For troubleshooting tips, click here." } , // NETWORK_PING,
9 : { "title" : "" , "message" : "" } , // BITRATE_THROTTLE_WARN,
10 : { "title" : "Low Bandwidth" , "message" : "The available bandwidth on your network has become too low,and this may impact your audio quality. For troubleshooting tips, click here." } , // BANDWIDTH_LOW
2014-02-19 06:27:18 +00:00
// IO related events
2013-09-06 02:27:43 +00:00
11 : { "title" : "Input Rate" , "message" : "The input rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click here." } , // INPUT_IO_RATE
12 : { "title" : "" , "message" : "" } , // INPUT_IO_JTR,
13 : { "title" : "Output Rate" , "message" : "The output rate of your audio device is varying too much to deliver good audio quality. For troubleshooting tips, click here." } , // OUTPUT_IO_RATE
14 : { "title" : "" , "message" : "" } , // OUTPUT_IO_JTR,
2014-02-19 06:27:18 +00:00
2013-09-06 02:27:43 +00:00
// CPU load related
15 : { "title" : "CPU Utilization High" , "message" : "The CPU of your computer is unable to keep up with the current processing load, and this may impact your audio quality. For troubleshooting tips, click here." } , // CPU_LOAD
16 : { "title" : "" , "message" : "" } , // DECODE_VIOLATIONS,
2014-02-19 06:27:18 +00:00
17 : { "title" : "" , "message" : "" } , // LAST_THRESHOLD
18 : { "title" : "" , "message" : "" } , // WIFI_NETWORK_ALERT, //user or peer is using wifi
19 : { "title" : "" , "message" : "" } , // NO_VALID_AUDIO_CONFIG, // alert the user to popup a config
20 : { "title" : "" , "message" : "" } , // AUDIO_DEVICE_NOT_PRESENT, // the audio device is not connected
21 : { "title" : "" , "message" : "" } , // RECORD_PLAYBACK_STATE, // record/playback events have occurred
22 : { "title" : "" , "message" : "" } , // RUN_UPDATE_CHECK_BACKGROUND, //this is auto check - do
23 : { "title" : "" , "message" : "" } , // RUN_UPDATE_CHECK_INTERACTIVE, //this is initiated by user
24 : { "title" : "" , "message" : "" } , // STUN_EVENT, // system completed stun test... come get the result
2014-02-21 03:46:54 +00:00
25 : { "title" : "No Audio" , "message" : "Your system is no longer transmitting audio. Other session members are unable to hear you." } , // DEAD_USER_WARN_EVENT, //the backend is not receiving audio from this peer
26 : { "title" : "No Audio" , "message" : "Your system is no longer transmitting audio. Other session members are unable to hear you." } , // DEAD_USER_REMOVE_EVENT, //the backend is removing the user from session as no audio is coming from this peer
2014-02-19 06:27:18 +00:00
27 : { "title" : "" , "message" : "" } , // WINDOW_CLOSE_BACKGROUND_MODE, //the user has closed the window and the client is now in background mode
28 : { "title" : "" , "message" : "" } , // WINDOW_OPEN_FOREGROUND_MODE, //the user has opened the window and the client is now in forground mode/
29 : { "title" : "" , "message" : "" } , // SESSION_LIVEBROADCAST_FAIL, //error of some sort - so can't broadcast
30 : { "title" : "" , "message" : "" } , // SESSION_LIVEBROADCAST_ACTIVE, //active
31 : { "title" : "" , "message" : "" } , // SESSION_LIVEBROADCAST_STOPPED, //stopped by server/user
32 : { "title" : "" , "message" : "" } , // SESSION_LIVEBROADCAST_PINNED, //node pinned by user
33 : { "title" : "" , "message" : "" } , // SESSION_LIVEBROADCAST_UNPINNED, //node unpinned by user
34 : { "title" : "" , "message" : "" } , // BACKEND_STATUS_MSG, //status/informational message
35 : { "title" : "" , "message" : "" } , // LOCAL_NETWORK_VARIANCE_HIGH,//the ping time via a hairpin for the user network is unnaturally high or variable.
//indicates problem with user computer stack or network itself (wifi, antivirus etc)
36 : { "title" : "" , "message" : "" } , // LOCAL_NETWORK_LATENCY_HIGH,
37 : { "title" : "" , "message" : "" } , // RECORDING_CLOSE, //update and remove tracks from front-end
38 : { "title" : "" , "message" : "" } // LAST_ALERT
2013-07-09 03:06:01 +00:00
} ;
2013-09-06 02:27:43 +00:00
2012-10-29 15:10:02 +00:00
function beforeShow ( data ) {
sessionId = data . id ;
2013-02-06 04:32:56 +00:00
$ ( '#session-mytracks-container' ) . empty ( ) ;
2013-11-03 20:55:55 +00:00
displayDoneRecording ( ) ; // assumption is that you can't join a recording session, so this should be safe
2014-02-07 05:57:31 +00:00
var shareDialog = new JK . ShareDialog ( context . JK . app , sessionId , "session" ) ;
shareDialog . initialize ( context . JK . FacebookHelperInstance ) ;
2012-10-29 15:10:02 +00:00
}
2012-10-29 00:37:59 +00:00
2013-08-18 02:36:07 +00:00
function alertCallback ( type , text ) {
2013-09-06 02:27:43 +00:00
if ( type === 2 ) { // BACKEND_MIXER_CHANGE
2013-11-16 04:35:40 +00:00
logger . debug ( "BACKEND_MIXER_CHANGE alert. reason:" + text ) ;
if ( sessionModel . id ( ) && text == "RebuildAudioIoControl" ) {
// this is a local change to our tracks. we need to tell the server about our updated track information
var inputTracks = context . JK . TrackHelpers . getUserTracks ( context . jamClient ) ;
// create a trackSync request based on backend data
var syncTrackRequest = { } ;
syncTrackRequest . client _id = app . clientId ;
syncTrackRequest . tracks = inputTracks ;
syncTrackRequest . id = sessionModel . id ( ) ;
rest . putTrackSyncChange ( syncTrackRequest )
. done ( function ( ) {
sessionModel . refreshCurrentSession ( ) ;
} )
. fail ( function ( ) {
app . notify ( {
"title" : "Can't Sync Local Tracks" ,
"text" : "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience." ,
"icon_url" : "/assets/content/icon_alert_big.png"
} ) ;
} )
}
else {
// this is wrong; we shouldn't be using the server to decide what tracks are shown
// however, we have to without a refactor, and if we wait a second, then it should be enough time
// for the client that initialed this to have made the change
// https://jamkazam.atlassian.net/browse/VRFS-854
setTimeout ( function ( ) {
sessionModel . refreshCurrentSession ( ) ; // XXX: race condition possible here; other client may not have updated server yet
} , 1000 ) ;
}
2014-02-19 06:27:18 +00:00
}
else if ( type === 24 ) { // STUN_EVENT
var testResults = context . jamClient . NetworkTestResult ( ) ;
$ . each ( testResults , function ( index , val ) {
if ( val . bStunFailed ) {
// if true we could not reach a stun server
}
else if ( val . bRemoteUdpBocked ) {
// if true the user cannot communicate with peer via UDP, although they could do LAN based session
}
} ) ;
}
else if ( type === 25 ) { // DEAD_USER_WARN_EVENT
2014-02-21 03:46:54 +00:00
app . notify ( {
"title" : alert _type [ type ] . title ,
"text" : text ,
"icon_url" : "/assets/content/icon_alert_big.png"
} ) ;
2014-02-19 06:27:18 +00:00
}
else if ( type === 26 ) { // DEAD_USER_REMOVE_EVENT
2014-02-21 03:46:54 +00:00
app . notify ( {
"title" : alert _type [ type ] . title ,
"text" : text ,
"icon_url" : "/assets/content/icon_alert_big.png"
} ) ;
2014-02-19 06:27:18 +00:00
}
else {
2013-09-06 02:27:43 +00:00
context . setTimeout ( function ( ) {
2013-11-16 04:35:40 +00:00
var alert = alert _type [ type ] ;
if ( alert ) {
app . notify ( {
"title" : alert _type [ type ] . title ,
"text" : text ,
"icon_url" : "/assets/content/icon_alert_big.png"
} ) ;
}
else {
logger . debug ( "Unknown Backend Event type %o, data %o" , type , text )
}
} , 1 ) ;
2013-09-06 02:27:43 +00:00
}
2013-07-09 03:06:01 +00:00
}
2012-10-06 16:36:05 +00:00
function afterShow ( data ) {
2013-05-31 02:07:33 +00:00
// indicate that the screen is active, so that
// body-scoped drag handlers can go active
screenActive = true ;
2013-01-30 16:50:43 +00:00
// Subscribe for callbacks on audio events
context . jamClient . SessionRegisterCallback ( "JK.HandleBridgeCallback" ) ;
2013-11-16 04:35:40 +00:00
context . jamClient . RegisterRecordingCallbacks ( "JK.HandleRecordingStartResult" , "JK.HandleRecordingStopResult" , "JK.HandleRecordingStarted" , "JK.HandleRecordingStopped" , "JK.HandleRecordingAborted" ) ;
2013-07-09 03:06:01 +00:00
context . jamClient . SessionSetAlertCallback ( "JK.AlertCallback" ) ;
2014-02-18 07:31:33 +00:00
context . jamClient . SessionSetConnectionStatusRefreshRate ( 1000 ) ;
2012-10-29 00:37:59 +00:00
2013-02-26 03:54:09 +00:00
// If you load this page directly, the loading of the current user
// is happening in parallel. We can't join the session until the
// current user has been completely loaded. Poll for the current user
// before proceeding with session joining.
function checkForCurrentUser ( ) {
if ( context . JK . userMe ) {
afterCurrentUserLoaded ( ) ;
} else {
context . setTimeout ( checkForCurrentUser , 100 ) ;
}
}
checkForCurrentUser ( ) ;
}
2013-11-16 04:35:40 +00:00
function notifyWithUserInfo ( title , text , clientId ) {
sessionModel . findUserBy ( { clientId : clientId } )
. done ( function ( user ) {
app . notify ( {
"title" : title ,
"text" : user . name + " " + text ,
"icon_url" : context . JK . resolveAvatarUrl ( user . photo _url )
} ) ;
} )
. fail ( function ( ) {
app . notify ( {
"title" : title ,
"text" : 'Someone ' + text ,
"icon_url" : "/assets/content/icon_alert_big.png"
} ) ;
} ) ;
}
2013-02-26 03:54:09 +00:00
function afterCurrentUserLoaded ( ) {
2013-08-31 13:54:11 +00:00
// It seems the SessionModel should be a singleton.
// a client can only be in one session at a time,
// and other parts of the code want to know at any certain times
// about the current session, if any (for example, reconnect logic)
context . JK . CurrentSessionModel = sessionModel = new context . JK . SessionModel (
2013-09-20 03:02:32 +00:00
context . JK . app ,
2013-03-02 19:02:42 +00:00
context . JK . JamServer ,
2013-08-31 13:54:11 +00:00
context . jamClient
2013-03-02 19:02:42 +00:00
) ;
2013-08-31 13:54:11 +00:00
2013-11-03 20:55:55 +00:00
$ ( sessionModel . recordingModel )
. on ( 'startingRecording' , function ( e , data ) {
2013-11-16 04:35:40 +00:00
displayStartingRecording ( ) ;
} )
. on ( 'startedRecording' , function ( e , data ) {
2013-11-03 20:55:55 +00:00
if ( data . reason ) {
2013-11-16 04:35:40 +00:00
var reason = data . reason ;
var detail = data . detail ;
var title = "Could Not Start Recording" ;
if ( data . reason == 'client-no-response' ) {
notifyWithUserInfo ( title , 'did not respond to the start signal.' , detail ) ;
}
else if ( data . reason == 'empty-recording-id' ) {
app . notifyAlert ( title , "No recording ID specified." ) ;
}
else if ( data . reason == 'missing-client' ) {
notifyWithUserInfo ( title , 'could not be signalled to start recording.' , detail ) ;
}
else if ( data . reason == 'already-recording' ) {
app . notifyAlert ( title , 'Already recording.' ) ;
}
else if ( data . reason == 'recording-engine-unspecified' ) {
notifyWithUserInfo ( title , 'had a problem writing recording data to disk.' , detail ) ;
}
else if ( data . reason == 'recording-engine-create-directory' ) {
notifyWithUserInfo ( title , 'had a problem creating a recording folder.' , detail ) ;
}
else if ( data . reason == 'recording-engine-create-file' ) {
notifyWithUserInfo ( title , 'had a problem creating a recording file.' , detail ) ;
}
else if ( data . reason == 'recording-engine-sample-rate' ) {
notifyWithUserInfo ( title , 'had a problem recording at the specified sample rate.' , detail ) ;
}
2014-01-05 03:47:23 +00:00
else if ( data . reason == 'rest' ) {
var jqXHR = detail [ 0 ] ;
app . notifyServerError ( jqXHR ) ;
}
2013-11-16 04:35:40 +00:00
else {
notifyWithUserInfo ( title , 'Error Reason: ' + reason ) ;
}
2013-11-03 20:55:55 +00:00
displayDoneRecording ( ) ;
}
2013-11-16 04:35:40 +00:00
else
{
displayStartedRecording ( ) ;
displayWhoCreated ( data . clientId ) ;
2013-11-03 20:55:55 +00:00
}
} )
. on ( 'stoppingRecording' , function ( e , data ) {
displayStoppingRecording ( data ) ;
} )
. on ( 'stoppedRecording' , function ( e , data ) {
if ( data . reason ) {
var reason = data . reason ;
2013-11-16 04:35:40 +00:00
var detail = data . detail ;
var title = "Recording Discarded" ;
2013-11-03 20:55:55 +00:00
if ( data . reason == 'client-no-response' ) {
2013-11-16 04:35:40 +00:00
notifyWithUserInfo ( title , 'did not respond to the stop signal.' , detail ) ;
2013-11-03 20:55:55 +00:00
}
2013-11-16 04:35:40 +00:00
else if ( data . reason == 'missing-client' ) {
notifyWithUserInfo ( title , 'could not be signalled to stop recording.' , detail ) ;
}
else if ( data . reason == 'empty-recording-id' ) {
app . notifyAlert ( title , "No recording ID specified." ) ;
}
else if ( data . reason == 'wrong-recording-id' ) {
app . notifyAlert ( title , "Wrong recording ID specified." ) ;
}
else if ( data . reason == 'not-recording' ) {
app . notifyAlert ( title , "Not currently recording." ) ;
}
else if ( data . reason == 'already-stopping' ) {
app . notifyAlert ( title , "Already stopping the current recording." ) ;
}
else if ( data . reason == 'start-before-stop' ) {
notifyWithUserInfo ( title , 'asked that we start a new recording; cancelling the current one.' , detail ) ;
}
else {
app . notifyAlert ( title , "Error reason: " + reason ) ;
}
2013-11-03 20:55:55 +00:00
displayDoneRecording ( ) ;
}
else {
displayDoneRecording ( ) ;
promptUserToSave ( data . recordingId ) ;
}
} )
2013-11-16 04:35:40 +00:00
. on ( 'abortedRecording' , function ( e , data ) {
var reason = data . reason ;
var detail = data . detail ;
2013-11-03 20:55:55 +00:00
2013-11-16 04:35:40 +00:00
var title = "Recording Cancelled" ;
2013-11-03 20:55:55 +00:00
2013-11-16 04:35:40 +00:00
if ( data . reason == 'client-no-response' ) {
notifyWithUserInfo ( title , 'did not respond to the start signal.' , detail ) ;
}
else if ( data . reason == 'missing-client' ) {
notifyWithUserInfo ( title , 'could not be signalled to start recording.' , detail ) ;
}
else if ( data . reason == 'populate-recording-info' ) {
notifyWithUserInfo ( title , 'could not synchronize with the server.' , detail ) ;
}
else if ( data . reason == 'recording-engine-unspecified' ) {
notifyWithUserInfo ( title , 'had a problem writing recording data to disk.' , detail ) ;
}
else if ( data . reason == 'recording-engine-create-directory' ) {
notifyWithUserInfo ( title , 'had a problem creating a recording folder.' , detail ) ;
}
else if ( data . reason == 'recording-engine-create-file' ) {
notifyWithUserInfo ( title , 'had a problem creating a recording file.' , detail ) ;
}
else if ( data . reason == 'recording-engine-sample-rate' ) {
notifyWithUserInfo ( title , 'had a problem recording at the specified sample rate.' , detail ) ;
}
else {
app . notifyAlert ( title , "Error reason: " + reason ) ;
}
displayDoneRecording ( ) ;
} )
2013-11-03 20:55:55 +00:00
2013-03-02 19:02:42 +00:00
sessionModel . subscribe ( 'sessionScreen' , sessionChanged ) ;
2013-08-31 13:54:11 +00:00
sessionModel . joinSession ( sessionId )
. fail ( function ( xhr , textStatus , errorMessage ) {
if ( xhr . status == 404 ) {
// we tried to join the session, but it's already gone. kick user back to join session screen
2014-02-06 13:03:44 +00:00
context . window . location = "/client#/findSession" ;
2013-08-31 13:54:11 +00:00
app . notify (
{ title : "Unable to Join Session" ,
text : "The session you attempted to join is over."
} ,
{ no _cancel : true } ) ;
2013-10-03 07:16:27 +00:00
}
else {
2013-08-31 13:54:11 +00:00
app . ajaxError ( xhr , textStatus , errorMessage ) ;
}
2013-09-05 04:24:28 +00:00
} ) ;
2013-02-10 18:51:37 +00:00
}
2012-10-29 00:37:59 +00:00
function beforeHide ( data ) {
2013-05-31 02:07:33 +00:00
// track that the screen is inactive, to disable body-level handlers
screenActive = false ;
2013-08-31 13:54:11 +00:00
sessionModel . leaveCurrentSession ( )
2013-09-05 04:24:28 +00:00
. fail ( app . ajaxError ) ;
2012-10-06 16:36:05 +00:00
}
2014-01-05 03:47:23 +00:00
function handleTransitionsInRecordingPlayback ( ) {
// let's see if we detect a transition to start playback or stop playback
var currentSession = sessionModel . getCurrentSession ( ) ;
if ( claimedRecording == null && ( currentSession && currentSession . claimed _recording != null ) ) {
// this is a 'started with a claimed_recording' transition.
// we need to start a timer to watch for the state of the play session
2014-01-06 20:35:35 +00:00
playbackControls . startMonitor ( ) ;
2014-01-05 03:47:23 +00:00
}
else if ( claimedRecording && ( currentSession == null || currentSession . claimed _recording == null ) ) {
2014-01-06 20:35:35 +00:00
playbackControls . stopMonitor ( ) ;
2014-01-05 03:47:23 +00:00
}
claimedRecording = currentSession == null ? null : currentSession . claimed _recording ;
}
2013-03-02 19:02:42 +00:00
function sessionChanged ( ) {
2013-09-06 12:34:57 +00:00
2014-01-05 03:47:23 +00:00
handleTransitionsInRecordingPlayback ( ) ;
2013-08-19 00:54:32 +00:00
// TODO - in the specific case of a user changing their tracks using the configureTrack dialog,
// this event appears to fire before the underlying mixers have updated. I have no event to
// know definitively when the underlying mixers are up to date, so for now, we just delay slightly.
// This obviously has the possibility of introducing time-based bugs.
2013-09-10 02:07:12 +00:00
context . setTimeout ( renderSession , RENDER _SESSION _DELAY ) ;
2013-08-19 00:54:32 +00:00
}
/ * *
* the mixers object is a list . In order to find one by key ,
* you must iterate . Convenience method to locate a particular
* mixer by id .
* /
function getMixer ( mixerId ) {
var foundMixer = null ;
$ . each ( mixers , function ( index , mixer ) {
if ( mixer . id === mixerId ) {
foundMixer = mixer ;
}
} ) ;
return foundMixer ;
2012-11-03 20:25:23 +00:00
}
function renderSession ( ) {
2013-02-14 07:02:05 +00:00
$ ( '#session-mytracks-container' ) . empty ( ) ;
2013-06-24 03:08:38 +00:00
$ ( '.session-track' ) . remove ( ) ; // Remove previous tracks
2013-09-06 02:27:43 +00:00
var $voiceChat = $ ( '#voice-chat' ) ;
$voiceChat . hide ( ) ;
2013-01-30 16:50:43 +00:00
_updateMixers ( ) ;
2012-11-03 20:25:23 +00:00
_renderTracks ( ) ;
2014-01-05 03:47:23 +00:00
_renderLocalMediaTracks ( ) ;
2013-02-07 04:58:41 +00:00
_wireTopVolume ( ) ;
2013-04-10 15:01:29 +00:00
_wireTopMix ( ) ;
2013-02-07 04:58:41 +00:00
_addVoiceChat ( ) ;
2013-05-22 05:03:34 +00:00
_initDialogs ( ) ;
2013-06-24 03:08:38 +00:00
if ( $ ( '.session-livetracks .track' ) . length === 0 ) {
$ ( '.session-livetracks .when-empty' ) . show ( ) ;
}
2014-01-05 03:47:23 +00:00
if ( $ ( '.session-recordings .track' ) . length === 0 ) {
$ ( '.session-recordings .when-empty' ) . show ( ) ;
$ ( '.session-recording-name-wrapper' ) . hide ( ) ;
2014-01-06 20:35:35 +00:00
$ ( '.session-recordings .recording-controls' ) . hide ( ) ;
2014-01-05 03:47:23 +00:00
}
2013-05-22 05:03:34 +00:00
}
2013-05-12 05:43:36 +00:00
2013-05-22 05:03:34 +00:00
function _initDialogs ( ) {
configureTrackDialog . initialize ( ) ;
2013-05-23 12:46:40 +00:00
addNewGearDialog . initialize ( ) ;
2013-01-30 16:50:43 +00:00
}
// Get the latest list of underlying audio mixer channels
function _updateMixers ( ) {
2013-01-31 16:32:32 +00:00
var mixerIds = context . jamClient . SessionGetIDs ( ) ;
2013-02-05 05:10:37 +00:00
var holder = $ . extend ( true , { } , { mixers : context . jamClient . SessionGetControlState ( mixerIds ) } ) ;
mixers = holder . mixers ;
2014-01-05 03:47:23 +00:00
2013-04-10 15:01:29 +00:00
// Always add a hard-coded simplified 'mixer' for the L2M mix
var l2m _mixer = {
id : '__L2M__' ,
range _low : - 80 ,
range _high : 20 ,
volume _left : context . jamClient . SessionGetMasterLocalMix ( )
} ;
mixers . push ( l2m _mixer ) ;
2012-10-21 23:03:46 +00:00
}
2014-01-05 03:47:23 +00:00
function _mixersForGroupId ( groupId ) {
var foundMixers = [ ] ;
$ . each ( mixers , function ( index , mixer ) {
if ( mixer . group _id === groupId ) {
foundMixers . push ( mixer ) ;
}
} ) ;
return foundMixers ;
}
2013-08-23 03:20:07 +00:00
// TODO FIXME - This needs to support multiple tracks for an individual
// client id and group.
2013-09-05 04:24:28 +00:00
function _mixerForClientId ( clientId , groupIds , usedMixers ) {
2013-02-14 07:02:05 +00:00
var foundMixer = null ;
$ . each ( mixers , function ( index , mixer ) {
if ( mixer . client _id === clientId ) {
for ( var i = 0 ; i < groupIds . length ; i ++ ) {
if ( mixer . group _id === groupIds [ i ] ) {
2013-09-05 04:24:28 +00:00
if ( ! ( mixer . id in usedMixers ) ) {
foundMixer = mixer ;
return false ;
}
2013-02-14 07:02:05 +00:00
}
}
2013-02-05 05:10:37 +00:00
}
} ) ;
2013-02-14 07:02:05 +00:00
return foundMixer ;
2013-01-30 16:50:43 +00:00
}
2013-02-07 04:58:41 +00:00
function _wireTopVolume ( ) {
2013-07-31 23:39:10 +00:00
var gainPercent = 0 ;
2013-03-14 03:29:57 +00:00
var mixerIds = [ ] ;
2013-02-07 04:58:41 +00:00
$ . each ( mixers , function ( index , mixer ) {
2013-02-26 03:54:09 +00:00
if ( mixer . group _id === ChannelGroupIds . MasterGroup ) {
2013-03-14 03:29:57 +00:00
mixerIds . push ( mixer . id ) ;
2013-07-31 23:39:10 +00:00
gainPercent = percentFromMixerValue (
2013-02-13 05:30:07 +00:00
mixer . range _low , mixer . range _high , mixer . volume _left ) ;
2013-02-07 04:58:41 +00:00
}
2013-02-26 03:54:09 +00:00
if ( mixer . group _id === ChannelGroupIds . MonitorGroup ) {
2013-03-14 03:29:57 +00:00
mixerIds . push ( mixer . id ) ;
2013-02-07 04:58:41 +00:00
}
} ) ;
2013-07-31 23:39:10 +00:00
var faderId = mixerIds . join ( ',' ) ;
$ ( '#volume' ) . attr ( 'mixer-id' , faderId ) ;
var faderOpts = {
faderId : faderId ,
faderType : "horizontal" ,
width : 50 ,
style : {
"background-image" : "none" ,
"background-repeat" : "no-repeat" ,
"height" : "24px"
}
} ;
context . JK . FaderHelpers . renderFader ( "#volume" , faderOpts ) ;
context . JK . FaderHelpers . subscribe ( faderId , faderChanged ) ;
// Visually update fader to underlying mixer start value.
2013-08-07 20:04:22 +00:00
// Always do this, even if gainPercent is zero.
2014-02-13 20:17:16 +00:00
2013-08-07 20:04:22 +00:00
context . JK . FaderHelpers . setFaderValue ( faderId , gainPercent ) ;
2013-02-07 04:58:41 +00:00
}
2013-04-10 15:01:29 +00:00
/ * *
* This control has it 's own Set/Get methods, so we don' t need to
* line it up with some mixer later . We ' ll use a special mixer - id value
* to let us know we ' re dealing with the mix control .
* /
function _wireTopMix ( ) {
var $mixSlider = $ ( '#l2m' ) ;
var l2m _mixer = {
range _low : - 80 ,
range _high : 20 ,
volume _left : context . jamClient . SessionGetMasterLocalMix ( )
} ;
2013-09-05 21:51:29 +00:00
// var gainPercent = percentFromMixerValue(
// l2m_mixer.range_low, l2m_mixer.range_high, l2m_mixer.volume_left);
2013-07-31 23:39:10 +00:00
var faderId = '#l2m' ; // also the selector for renderFader
var faderOpts = {
faderId : faderId ,
faderType : "horizontal" ,
width : 70 ,
style : {
"background-image" : "none" ,
"background-repeat" : "no-repeat" ,
"height" : "24px"
}
} ;
context . JK . FaderHelpers . renderFader ( faderId , faderOpts ) ;
context . JK . FaderHelpers . subscribe ( faderId , l2mChanged ) ;
2013-09-05 21:51:29 +00:00
// initialize to middle (50%, 0dB) per Peter's request
context . JK . FaderHelpers . setFaderValue ( faderId , 50 ) ;
context . jamClient . SessionSetMasterLocalMix ( 0 ) ;
2013-07-31 23:39:10 +00:00
}
/ * *
* This has a specialized jamClient call , so custom handler .
* /
function l2mChanged ( faderId , newValue , dragging ) {
2013-09-05 21:51:29 +00:00
var dbValue = context . JK . FaderHelpers . convertLinearToDb ( newValue ) ;
2013-07-31 23:39:10 +00:00
context . jamClient . SessionSetMasterLocalMix ( dbValue ) ;
2013-04-10 15:01:29 +00:00
}
2013-02-07 04:58:41 +00:00
function _addVoiceChat ( ) {
2013-02-08 05:08:05 +00:00
// If, and only if, there is a mixer in group 3 (voice chat)
// Add the voice chat controls below my tracks, and hook up the mixer.
// Assumption is that there is only ever one, so we just take the first one.
$ . each ( mixers , function ( index , mixer ) {
2013-02-26 03:54:09 +00:00
if ( mixer . group _id === ChannelGroupIds . AudioInputChatGroup ) {
2013-02-08 05:08:05 +00:00
var $voiceChat = $ ( '#voice-chat' ) ;
$voiceChat . show ( ) ;
$voiceChat . attr ( 'mixer-id' , mixer . id ) ;
$ ( '#voice-chat .voicechat-mute' ) . attr ( 'mixer-id' , mixer . id ) ;
2013-02-13 05:30:07 +00:00
var gainPercent = percentFromMixerValue (
mixer . range _low , mixer . range _high , mixer . volume _left ) ;
2013-07-31 23:39:10 +00:00
var faderOpts = {
faderId : mixer . id ,
faderType : "horizontal" ,
width : 50
} ;
context . JK . FaderHelpers . renderFader ( "#voice-chat .voicechat-gain" , faderOpts ) ;
context . JK . FaderHelpers . subscribe ( mixer . id , faderChanged ) ;
2013-08-07 20:04:22 +00:00
context . JK . FaderHelpers . setFaderValue ( mixer . id , gainPercent ) ;
2013-02-13 05:30:07 +00:00
if ( mixer . mute ) {
var $mute = $voiceChat . find ( '.voicechat-mute' ) ;
_toggleVisualMuteControl ( $mute , true ) ;
}
2013-02-08 05:08:05 +00:00
}
} ) ;
2013-02-07 04:58:41 +00:00
}
2014-01-05 03:47:23 +00:00
function _renderLocalMediaTracks ( ) {
var localMediaMixers = _mixersForGroupId ( ChannelGroupIds . MediaTrackGroup ) ;
if ( localMediaMixers . length == 0 ) {
localMediaMixers = _mixersForGroupId ( ChannelGroupIds . PeerMediaTrackGroup ) ;
}
var recordedTracks = sessionModel . recordedTracks ( ) ;
2014-01-08 21:24:21 +00:00
console . log ( "recorded tracks=%o local_media=%o" , recordedTracks , localMediaMixers ) ;
2014-01-05 03:47:23 +00:00
if ( recordedTracks && localMediaMixers . length == 0 ) {
// if we are the creator, then rather than raise an error, tell the server the recording is over.
// this shoudl only happen if we get temporarily disconnected by forced reload, which isn't a very normal scenario
if ( sessionModel . getCurrentSession ( ) . claimed _recording _initiator _id == context . JK . userMe . id ) {
closeRecording ( ) ;
return ;
}
}
if ( recordedTracks ) {
$ ( '.session-recording-name' ) . text ( sessionModel . getCurrentSession ( ) . claimed _recording . name ) ;
var noCorrespondingTracks = false ;
$ . each ( localMediaMixers , function ( index , mixer ) {
var preMasteredClass = "" ;
// find the track or tracks that correspond to the mixer
var correspondingTracks = [ ]
$ . each ( recordedTracks , function ( i , recordedTrack ) {
if ( mixer . id . indexOf ( "L" ) == 0 ) {
if ( mixer . id . substring ( 1 ) == recordedTrack . client _track _id ) {
correspondingTracks . push ( recordedTrack ) ;
}
}
else if ( mixer . id . indexOf ( "C" ) == 0 ) {
if ( mixer . id . substring ( 1 ) == recordedTrack . client _id ) {
correspondingTracks . push ( recordedTrack ) ;
preMasteredClass = "pre-mastered-track" ;
}
}
else {
// this should not be possible
alert ( "Invalid state: the recorded track had neither persisted_track_id or persisted_client_id" ) ;
}
} ) ;
if ( correspondingTracks . length == 0 ) {
noCorrespondingTracks = true ;
app . notify ( {
title : "Unable to Open Recording" ,
text : "Could not correlate server and client tracks" ,
icon _url : "/assets/content/icon_alert_big.png" } ) ;
return false ;
}
// prune found recorded tracks
recordedTracks = $ . grep ( recordedTracks , function ( value ) {
return $ . inArray ( value , correspondingTracks ) < 0 ;
} ) ;
var oneOfTheTracks = correspondingTracks [ 0 ] ;
var instrumentIcon = context . JK . getInstrumentIcon45 ( oneOfTheTracks . instrument _id ) ;
var photoUrl = "/assets/content/icon_recording.png" ;
var name = oneOfTheTracks . user . name ;
if ( ! ( name ) ) {
name = oneOfTheTracks . user . first _name + ' ' + oneOfTheTracks . user . last _name ;
}
// Default trackData to participant + no Mixer state.
var trackData = {
trackId : oneOfTheTracks . id ,
clientId : oneOfTheTracks . client _id ,
name : name ,
instrumentIcon : instrumentIcon ,
avatar : photoUrl ,
latency : "good" ,
gainPercent : 0 ,
muteClass : 'muted' ,
mixerId : "" ,
avatarClass : 'avatar-recording' ,
preMasteredClass : preMasteredClass
} ;
var gainPercent = percentFromMixerValue (
mixer . range _low , mixer . range _high , mixer . volume _left ) ;
var muteClass = "enabled" ;
if ( mixer . mute ) {
muteClass = "muted" ;
}
trackData . gainPercent = gainPercent ;
trackData . muteClass = muteClass ;
trackData . mixerId = mixer . id ;
_addMediaTrack ( index , trackData ) ;
} ) ;
if ( ! noCorrespondingTracks && recordedTracks . length > 0 ) {
logger . error ( "unable to find all recorded tracks against client tracks" ) ;
app . notify ( { title : "All tracks not found" ,
text : "Some tracks in the recording are not present in the playback" ,
icon _url : "/assets/content/icon_alert_big.png" } )
}
}
}
2012-11-03 20:25:23 +00:00
function _renderTracks ( ) {
2013-05-15 05:59:09 +00:00
myTracks = [ ] ;
2013-02-14 07:02:05 +00:00
// Participants are here now, but the mixers don't update right away.
// Draw tracks from participants, then setup timers to look for the
// mixers that go with those participants, if they're missing.
lookingForMixersCount = 0 ;
2013-03-02 19:02:42 +00:00
$ . each ( sessionModel . participants ( ) , function ( index , participant ) {
2013-02-14 07:02:05 +00:00
var name = participant . user . name ;
if ( ! ( name ) ) {
name = participant . user . first _name + ' ' + participant . user . last _name ;
}
2013-09-05 04:24:28 +00:00
var usedMixers = { } ; // Once we use a mixer, we add it here to allow us to find 'second' tracks
2013-05-15 05:59:09 +00:00
// loop through all tracks for each participant
$ . each ( participant . tracks , function ( index , track ) {
var instrumentIcon = context . JK . getInstrumentIcon45 ( track . instrument _id ) ;
var photoUrl = context . JK . resolveAvatarUrl ( participant . user . photo _url ) ;
var myTrack = false ;
// Default trackData to participant + no Mixer state.
var trackData = {
trackId : track . id ,
2013-05-22 11:48:37 +00:00
connection _id : track . connection _id ,
2013-05-15 05:59:09 +00:00
clientId : participant . client _id ,
name : name ,
instrumentIcon : instrumentIcon ,
avatar : photoUrl ,
latency : "good" ,
gainPercent : 0 ,
muteClass : 'muted' ,
2014-01-05 03:47:23 +00:00
mixerId : "" ,
avatarClass : 'avatar-med' ,
preMasteredClass : ""
2013-05-15 05:59:09 +00:00
} ;
2013-08-23 03:20:07 +00:00
// This is the likely cause of multi-track problems.
// This should really become _mixersForClientId and return a list.
// With multiple tracks, there will be more than one mixer for a
// particular client, in a particular group, and I'll need to further
// identify by track id or something similar.
2013-05-15 05:59:09 +00:00
var mixer = _mixerForClientId (
participant . client _id ,
[
ChannelGroupIds . AudioInputMusicGroup ,
2013-08-23 03:20:07 +00:00
ChannelGroupIds . PeerAudioInputMusicGroup
2013-09-05 04:24:28 +00:00
] ,
usedMixers ) ;
2013-05-15 05:59:09 +00:00
if ( mixer ) {
2013-09-05 04:24:28 +00:00
usedMixers [ mixer . id ] = true ;
2013-05-15 05:59:09 +00:00
myTrack = ( mixer . group _id === ChannelGroupIds . AudioInputMusicGroup ) ;
var gainPercent = percentFromMixerValue (
mixer . range _low , mixer . range _high , mixer . volume _left ) ;
var muteClass = "enabled" ;
if ( mixer . mute ) {
muteClass = "muted" ;
}
trackData . gainPercent = gainPercent ;
trackData . muteClass = muteClass ;
trackData . mixerId = mixer . id ;
2013-09-24 07:22:41 +00:00
context . jamClient . SessionSetUserName ( participant . client _id , name ) ;
2013-05-15 05:59:09 +00:00
} else { // No mixer to match, yet
2013-09-05 04:24:28 +00:00
lookingForMixers [ track . id ] = participant . client _id ;
2013-05-15 05:59:09 +00:00
if ( ! ( lookingForMixersTimer ) ) {
2013-08-19 00:59:49 +00:00
lookingForMixersTimer = context . setInterval ( lookForMixers , 500 ) ;
2013-05-15 05:59:09 +00:00
}
2013-02-10 02:00:29 +00:00
}
2013-09-06 12:34:57 +00:00
2013-05-22 05:03:34 +00:00
_addTrack ( index , trackData ) ;
2013-05-15 05:59:09 +00:00
// Show settings icons only for my tracks
if ( myTrack ) {
2013-05-24 01:16:00 +00:00
myTracks . push ( trackData ) ;
2013-09-06 02:04:36 +00:00
}
2013-05-24 01:16:00 +00:00
// TODO: UNCOMMENT THIS WHEN TESTING LOCALLY IN BROWSER
2013-08-15 05:12:59 +00:00
//myTracks.push(trackData);
2013-05-15 05:59:09 +00:00
} ) ;
2012-11-03 20:25:23 +00:00
} ) ;
2013-05-22 05:03:34 +00:00
configureTrackDialog = new context . JK . ConfigureTrackDialog ( app , myTracks , sessionId , sessionModel ) ;
2013-09-12 12:02:13 +00:00
addNewGearDialog = new context . JK . AddNewGearDialog ( app , ftueCallback ) ;
2012-11-03 20:25:23 +00:00
}
2013-09-12 12:02:13 +00:00
function ftueCallback ( ) {
2014-02-06 13:03:44 +00:00
context . location = "/client#/home" ;
2013-09-12 12:02:13 +00:00
app . layout . showDialog ( 'ftue' ) ;
}
2013-08-28 03:05:53 +00:00
function connectTrackToMixer ( trackSelector , clientId , mixerId , gainPercent ) {
var vuOpts = $ . extend ( { } , trackVuOpts ) ;
var faderOpts = $ . extend ( { } , trackFaderOpts ) ;
faderOpts . faderId = mixerId ;
var vuLeftSelector = trackSelector + " .track-vu-left" ;
var vuRightSelector = trackSelector + " .track-vu-right" ;
var faderSelector = trackSelector + " .track-gain" ;
2013-09-05 04:24:28 +00:00
var $track = $ ( trackSelector ) ;
2013-08-28 03:05:53 +00:00
// Set mixer-id attributes and render VU/Fader
context . JK . VuHelpers . renderVU ( vuLeftSelector , vuOpts ) ;
$track . find ( '.track-vu-left' ) . attr ( 'mixer-id' , mixerId + '_vul' ) ;
context . JK . VuHelpers . renderVU ( vuRightSelector , vuOpts ) ;
$track . find ( '.track-vu-right' ) . attr ( 'mixer-id' , mixerId + '_vur' ) ;
context . JK . FaderHelpers . renderFader ( faderSelector , faderOpts ) ;
// Set gain position
context . JK . FaderHelpers . setFaderValue ( mixerId , gainPercent ) ;
context . JK . FaderHelpers . subscribe ( mixerId , faderChanged ) ;
}
2013-02-14 07:02:05 +00:00
// Function called on an interval when participants change. Mixers seem to
// show up later, so we render the tracks from participants, but keep track
// of the ones there weren't any mixers for, and continually try to find them
// and get them connected to the mixers underneath.
function lookForMixers ( ) {
lookingForMixersCount ++ ;
_updateMixers ( ) ;
2013-09-05 04:24:28 +00:00
var usedMixers = { } ;
2013-02-14 07:02:05 +00:00
var keysToDelete = [ ] ;
for ( var key in lookingForMixers ) {
2013-09-05 04:24:28 +00:00
var clientId = lookingForMixers [ key ] ;
2013-02-26 03:54:09 +00:00
var mixer = _mixerForClientId (
2013-09-05 04:24:28 +00:00
clientId ,
2013-02-26 03:54:09 +00:00
[
ChannelGroupIds . AudioInputMusicGroup ,
2013-08-23 03:20:07 +00:00
ChannelGroupIds . PeerAudioInputMusicGroup
2013-09-05 04:24:28 +00:00
] ,
usedMixers ) ;
2013-02-14 07:02:05 +00:00
if ( mixer ) {
2013-09-05 04:24:28 +00:00
usedMixers [ mixer . id ] = true ;
2013-02-14 07:02:05 +00:00
keysToDelete . push ( key ) ;
var gainPercent = percentFromMixerValue (
mixer . range _low , mixer . range _high , mixer . volume _left ) ;
2013-09-05 04:24:28 +00:00
var trackSelector = 'div.track[track-id="' + key + '"]' ;
2013-08-28 03:05:53 +00:00
connectTrackToMixer ( trackSelector , key , mixer . id , gainPercent ) ;
2013-02-14 07:02:05 +00:00
var $track = $ ( 'div.track[client-id="' + key + '"]' ) ;
$track . find ( '.track-icon-mute' ) . attr ( 'mixer-id' , mixer . id ) ;
// Set mute state
_toggleVisualMuteControl ( $track . find ( '.track-icon-mute' ) , mixer . mute ) ;
}
}
for ( var i = 0 ; i < keysToDelete . length ; i ++ ) {
delete lookingForMixers [ keysToDelete [ i ] ] ;
}
if ( context . JK . dlen ( lookingForMixers ) === 0 ||
2013-08-19 00:59:49 +00:00
lookingForMixersCount > 20 ) {
2013-02-14 07:02:05 +00:00
lookingForMixersCount = 0 ;
lookingForMixers = { } ;
context . clearTimeout ( lookingForMixersTimer ) ;
lookingForMixersTimer = null ;
}
}
2013-01-30 16:50:43 +00:00
// Given a mixerID and a value between 0.0-1.0,
// light up the proper VU lights.
function _updateVU ( mixerId , value ) {
2013-08-19 00:54:32 +00:00
// Special-case for mono tracks. If mono, and it's a _vul id,
// update both sides, otherwise do nothing.
// If it's a stereo track, just do the normal thing.
var selector ;
var pureMixerId = mixerId . replace ( "_vul" , "" ) ;
pureMixerId = pureMixerId . replace ( "_vur" , "" ) ;
var mixer = getMixer ( pureMixerId ) ;
if ( mixer ) {
if ( ! ( mixer . stereo ) ) { // mono track
if ( mixerId . substr ( - 4 ) === "_vul" ) {
// Do the left
selector = '#tracks [mixer-id="' + pureMixerId + '_vul"]' ;
context . JK . VuHelpers . updateVU ( selector , value ) ;
// Do the right
selector = '#tracks [mixer-id="' + pureMixerId + '_vur"]' ;
context . JK . VuHelpers . updateVU ( selector , value ) ;
} // otherwise, it's a mono track, _vur event - ignore.
} else { // stereo track
selector = '#tracks [mixer-id="' + mixerId + '"]' ;
context . JK . VuHelpers . updateVU ( selector , value ) ;
}
}
2013-01-30 16:50:43 +00:00
}
2013-05-22 05:03:34 +00:00
function _addTrack ( index , trackData ) {
2013-07-20 15:38:52 +00:00
var parentSelector = '#session-mytracks-container' ;
var $destination = $ ( parentSelector ) ;
2013-02-05 05:10:37 +00:00
if ( trackData . clientId !== app . clientId ) {
2013-07-20 15:38:52 +00:00
parentSelector = '#session-livetracks-container' ;
$destination = $ ( parentSelector ) ;
2013-06-24 03:08:38 +00:00
$ ( '.session-livetracks .when-empty' ) . hide ( ) ;
2013-02-05 05:10:37 +00:00
}
2012-10-21 23:03:46 +00:00
var template = $ ( '#template-session-track' ) . html ( ) ;
2014-01-05 03:47:23 +00:00
var newTrack = $ ( context . JK . fillTemplate ( template , trackData ) ) ;
2013-02-05 05:10:37 +00:00
$destination . append ( newTrack ) ;
2013-05-22 05:03:34 +00:00
2013-07-26 14:46:38 +00:00
// Render VU meters and gain fader
2013-09-05 04:24:28 +00:00
var trackSelector = parentSelector + ' .session-track[track-id="' + trackData . trackId + '"]' ;
2013-08-28 03:05:53 +00:00
var gainPercent = trackData . gainPercent || 0 ;
connectTrackToMixer ( trackSelector , trackData . clientId , trackData . mixerId , gainPercent ) ;
2013-07-20 15:38:52 +00:00
2013-05-22 05:03:34 +00:00
var $closeButton = $ ( '#div-track-close' , 'div[track-id="' + trackData . trackId + '"]' ) ;
if ( index === 0 ) {
$closeButton . hide ( ) ;
}
else {
$closeButton . click ( deleteTrack ) ;
}
2013-05-15 05:59:09 +00:00
tracks [ trackData . trackId ] = new context . JK . SessionTrack ( trackData . clientId ) ;
2012-10-21 23:03:46 +00:00
}
2014-01-05 03:47:23 +00:00
function _addMediaTrack ( index , trackData ) {
var parentSelector = '#session-recordedtracks-container' ;
var $destination = $ ( parentSelector ) ;
$ ( '.session-recordings .when-empty' ) . hide ( ) ;
$ ( '.session-recording-name-wrapper' ) . show ( ) ;
2014-01-06 20:35:35 +00:00
$ ( '.session-recordings .recording-controls' ) . show ( ) ;
2014-01-05 03:47:23 +00:00
var template = $ ( '#template-session-track' ) . html ( ) ;
var newTrack = $ ( context . JK . fillTemplate ( template , trackData ) ) ;
$destination . append ( newTrack ) ;
if ( trackData . preMasteredClass ) {
context . JK . helpBubble ( $ ( '.track-instrument' , newTrack ) , 'pre-processed-track' , { } , { offsetParent : newTrack . closest ( '.content-body' ) } ) ;
}
// Render VU meters and gain fader
var trackSelector = parentSelector + ' .session-track[track-id="' + trackData . trackId + '"]' ;
var gainPercent = trackData . gainPercent || 0 ;
connectTrackToMixer ( trackSelector , trackData . clientId , trackData . mixerId , gainPercent ) ;
tracks [ trackData . trackId ] = new context . JK . SessionTrack ( trackData . clientId ) ;
}
2013-07-26 14:46:38 +00:00
/ * *
* Will be called when fader changes . The fader id ( provided at subscribe time ) ,
* the new value ( 0 - 100 ) and whether the fader is still being dragged are passed .
* /
function faderChanged ( faderId , newValue , dragging ) {
var mixerIds = faderId . split ( ',' ) ;
$ . each ( mixerIds , function ( i , v ) {
2013-08-02 21:16:49 +00:00
var broadcast = ! ( dragging ) ; // If fader is still dragging, don't broadcast
fillTrackVolumeObject ( v , broadcast ) ;
2013-07-26 14:46:38 +00:00
setMixerVolume ( v , newValue ) ;
} ) ;
}
2013-10-05 18:24:22 +00:00
function handleVolumeChangeCallback ( mixerId , isLeft , value , isMuted ) {
2013-08-07 20:04:22 +00:00
// Visually update mixer
2013-08-27 02:31:18 +00:00
// There is no need to actually set the back-end mixer value as the
// back-end will already have updated the audio mixer directly prior to sending
// me this event. I simply need to visually show the new fader position.
2013-08-07 20:04:22 +00:00
// TODO: Use mixer's range
var faderValue = percentFromMixerValue ( - 80 , 20 , value ) ;
context . JK . FaderHelpers . setFaderValue ( mixerId , faderValue ) ;
2013-10-05 18:24:22 +00:00
var $muteControl = $ ( '[control="mute"][mixer-id="' + mixerId + '"]' ) ;
_toggleVisualMuteControl ( $muteControl , isMuted ) ;
2013-08-07 20:04:22 +00:00
}
2013-01-31 05:23:30 +00:00
function handleBridgeCallback ( ) {
var eventName = null ;
var mixerId = null ;
var value = null ;
var tuples = arguments . length / 3 ;
for ( var i = 0 ; i < tuples ; i ++ ) {
eventName = arguments [ 3 * i ] ;
mixerId = arguments [ ( 3 * i ) + 1 ] ;
value = arguments [ ( 3 * i ) + 2 ] ;
var vuVal = 0.0 ;
if ( eventName === 'left_vu' || eventName === 'right_vu' ) {
// TODO - no guarantee range will be -80 to 20. Get from the
// GetControlState for this mixer which returns min/max
// value is a DB value from -80 to 20. Convert to float from 0.0-1.0
vuVal = ( value + 80 ) / 100 ;
if ( eventName === 'left_vu' ) {
2013-01-31 16:32:32 +00:00
mixerId = mixerId + "_vul" ;
2013-01-31 05:23:30 +00:00
} else {
2013-01-31 16:32:32 +00:00
mixerId = mixerId + "_vur" ;
2013-01-31 05:23:30 +00:00
}
_updateVU ( mixerId , vuVal ) ;
2013-10-12 22:35:12 +00:00
} else if ( eventName === 'connection_status' ) {
// Connection Quality Change
var connectionClass = 'green' ;
if ( value < 7 ) {
connectionClass = 'yellow' ;
}
if ( value < 4 ) {
connectionClass = 'red' ;
}
var $connection = $ ( '[mixer-id="' + mixerId + '_connection"]' ) ;
$connection . removeClass ( 'green yellow red' ) ;
$connection . addClass ( connectionClass ) ;
2013-02-15 05:15:36 +00:00
} else if ( eventName === 'add' || eventName === 'remove' ) {
// TODO - _renderSession. Note I get streams of these in
// sequence, so have Nat fix, or buffer/spam protect
// Note - this is already handled from websocket events.
// However, there may be use of these two events to avoid
// the polling-style check for when a mixer has been added
// to match a participant track.
2013-01-31 16:32:32 +00:00
} else {
// Examples of other events
// Add media file track: "add", "The_Abyss_4T", 0
2013-11-06 02:50:32 +00:00
logger . debug ( 'non-vu event: ' + eventName + ',' + mixerId + ',' + value ) ;
2013-01-31 05:23:30 +00:00
}
2013-01-30 16:50:43 +00:00
}
}
2012-10-06 16:36:05 +00:00
function deleteSession ( evt ) {
2012-12-07 00:28:48 +00:00
var sessionId = $ ( evt . currentTarget ) . attr ( "action-id" ) ;
2012-10-06 16:36:05 +00:00
if ( sessionId ) {
$ . ajax ( {
type : "DELETE" ,
2013-02-26 03:54:09 +00:00
url : "/api/sessions/" + sessionId ,
success : function ( response ) {
2014-02-06 13:03:44 +00:00
context . location = "/client#/home" ;
2013-02-26 03:54:09 +00:00
} ,
error : function ( jqXHR , textStatus , errorThrown ) {
logger . error ( "Error deleting session " + sessionId ) ;
}
} ) ;
2012-10-06 16:36:05 +00:00
}
}
2013-05-22 05:03:34 +00:00
function deleteTrack ( evt ) {
var trackId = $ ( evt . currentTarget ) . attr ( "track-id" ) ;
2013-05-23 12:46:40 +00:00
sessionModel . deleteTrack ( sessionId , trackId ) ;
2013-05-22 05:03:34 +00:00
}
2013-01-31 16:32:32 +00:00
function _toggleVisualMuteControl ( $control , muting ) {
if ( muting ) {
$control . removeClass ( 'enabled' ) ;
$control . addClass ( 'muted' ) ;
} else {
$control . removeClass ( 'muted' ) ;
$control . addClass ( 'enabled' ) ;
}
}
function _toggleAudioMute ( mixerId , muting ) {
2013-03-15 02:44:51 +00:00
fillTrackVolumeObject ( mixerId ) ;
2013-01-31 16:32:32 +00:00
context . trackVolumeObject . mute = muting ;
context . jamClient . SessionSetControlState ( mixerId ) ;
}
function toggleMute ( evt ) {
var $control = $ ( evt . currentTarget ) ;
var muting = ( $control . hasClass ( 'enabled' ) ) ;
2013-03-15 02:44:51 +00:00
var mixerIds = $control . attr ( 'mixer-id' ) . split ( ',' ) ;
$ . each ( mixerIds , function ( i , v ) {
_toggleAudioMute ( v , muting ) ;
} ) ;
2013-01-31 16:32:32 +00:00
_toggleVisualMuteControl ( $control , muting ) ;
}
2013-03-24 20:47:45 +00:00
function fillTrackVolumeObject ( mixerId , broadcast ) {
2013-03-15 02:44:51 +00:00
_updateMixers ( ) ;
2013-02-03 22:47:17 +00:00
var mixer = null ;
2013-03-24 20:47:45 +00:00
var _broadcast = true ;
if ( broadcast !== undefined ) {
_broadcast = broadcast ;
}
2013-02-03 22:47:17 +00:00
for ( var i = 0 ; i < mixers . length ; i ++ ) {
mixer = mixers [ i ] ;
if ( mixer . id === mixerId ) {
context . trackVolumeObject . clientID = mixer . client _id ;
2013-03-24 20:47:45 +00:00
context . trackVolumeObject . broadcast = _broadcast ;
2013-02-03 22:47:17 +00:00
context . trackVolumeObject . master = mixer . master ;
context . trackVolumeObject . monitor = mixer . monitor ;
context . trackVolumeObject . mute = mixer . mute ;
context . trackVolumeObject . name = mixer . name ;
context . trackVolumeObject . record = mixer . record ;
context . trackVolumeObject . volL = mixer . volume _left ;
context . trackVolumeObject . volR = mixer . volume _right ;
// trackVolumeObject doesn't have a place for range min/max
currentMixerRangeMin = mixer . range _low ;
currentMixerRangeMax = mixer . range _high ;
break ;
}
}
}
2013-02-10 02:00:29 +00:00
// Given a mixer's min/max and current value, return it as
// a percent from 0-100. Return an integer.
function percentFromMixerValue ( min , max , value ) {
2013-08-07 20:04:22 +00:00
try {
var range = Math . abs ( max - min ) ;
var magnitude = value - min ;
var percent = Math . round ( 100 * ( magnitude / range ) ) ;
return percent ;
} catch ( err ) {
return 0 ;
}
2013-02-10 02:00:29 +00:00
}
// Given a mixer's min/max and a percent value, return it as
// the mixer's value. Returns an integer.
function percentToMixerValue ( min , max , percent ) {
var range = Math . abs ( max - min ) ;
var multiplier = percent / 100 ; // Change 85 into 0.85
var value = min + ( multiplier * range ) ;
// Protect against percents < 0 and > 100
if ( value < min ) {
value = min ;
}
if ( value > max ) {
value = max ;
}
return value ;
}
2013-02-03 22:47:17 +00:00
2013-02-10 02:00:29 +00:00
// Given a volume percent (0-100), set the underlying
2013-02-03 19:48:39 +00:00
// audio volume level of the passed mixerId to the correct
// value.
function setMixerVolume ( mixerId , volumePercent ) {
2013-02-03 22:47:17 +00:00
// The context.trackVolumeObject has been filled with the mixer values
// that go with mixerId, and the range of that mixer
// has been set in currentMixerRangeMin-Max.
// All that needs doing is to translate the incoming percent
// into the real value ont the sliders range. Set Left/Right
// volumes on trackVolumeObject, and call SetControlState to stick.
2013-02-10 02:00:29 +00:00
var sliderValue = percentToMixerValue (
currentMixerRangeMin , currentMixerRangeMax , volumePercent ) ;
2013-02-03 22:47:17 +00:00
context . trackVolumeObject . volL = sliderValue ;
context . trackVolumeObject . volR = sliderValue ;
2013-04-10 15:01:29 +00:00
// Special case for L2M mix:
if ( mixerId === '__L2M__' ) {
2013-09-05 21:51:29 +00:00
logger . debug ( "L2M volumePercent=" + volumePercent ) ;
var dbValue = context . JK . FaderHelpers . convertLinearToDb ( volumePercent ) ;
context . jamClient . SessionSetMasterLocalMix ( dbValue ) ;
// context.jamClient.SessionSetMasterLocalMix(sliderValue);
2013-04-10 15:01:29 +00:00
} else {
context . jamClient . SessionSetControlState ( mixerId ) ;
}
2013-02-03 19:48:39 +00:00
}
2013-09-14 17:27:03 +00:00
function sessionResync ( evt ) {
evt . preventDefault ( ) ;
2013-09-16 01:55:12 +00:00
var response = context . jamClient . SessionAudioResync ( ) ;
2013-09-14 17:27:03 +00:00
if ( response ) {
app . notify ( {
"title" : "Error" ,
"text" : response ,
"icon_url" : "/assets/content/icon_alert_big.png" } ) ;
}
return false ;
}
2013-11-03 20:55:55 +00:00
// http://stackoverflow.com/questions/2604450/how-to-create-a-jquery-clock-timer
function updateRecordingTimer ( ) {
function pretty _time _string ( num ) {
return ( num < 10 ? "0" : "" ) + num ;
}
var total _seconds = ( new Date - startTimeDate ) / 1000 ;
var hours = Math . floor ( total _seconds / 3600 ) ;
total _seconds = total _seconds % 3600 ;
var minutes = Math . floor ( total _seconds / 60 ) ;
total _seconds = total _seconds % 60 ;
var seconds = Math . floor ( total _seconds ) ;
hours = pretty _time _string ( hours ) ;
minutes = pretty _time _string ( minutes ) ;
seconds = pretty _time _string ( seconds ) ;
if ( hours > 0 ) {
var currentTimeString = hours + ":" + minutes + ":" + seconds ;
}
else {
var currentTimeString = minutes + ":" + seconds ;
}
$recordingTimer . text ( '(' + currentTimeString + ')' ) ;
}
function displayStartingRecording ( ) {
$ ( '#recording-start-stop' ) . addClass ( 'currently-recording' ) ;
$ ( '#recording-status' ) . text ( "Starting..." )
}
function displayStartedRecording ( ) {
startTimeDate = new Date ;
$recordingTimer = $ ( "<span id='recording-timer'>(0:00)</span>" ) ;
var $recordingStatus = $ ( '<span></span>' ) . append ( "<span>Stop Recording</span>" ) . append ( $recordingTimer ) ;
$ ( '#recording-status' ) . html ( $recordingStatus ) ;
recordingTimerInterval = setInterval ( updateRecordingTimer , 1000 ) ;
}
function displayStoppingRecording ( data ) {
if ( data ) {
if ( data . reason ) {
app . notify ( {
"title" : "Recording Aborted" ,
"text" : "The recording was aborted due to '" + data . reason + '"' ,
"icon_url" : "/assets/content/icon_alert_big.png"
} ) ;
}
}
$ ( '#recording-status' ) . text ( "Stopping..." ) ;
}
function displayDoneRecording ( ) {
if ( recordingTimerInterval ) {
clearInterval ( recordingTimerInterval ) ;
recordingTimerInterval = null ;
startTimeDate = null ;
}
$recordingTimer = null ;
$ ( '#recording-start-stop' ) . removeClass ( 'currently-recording' ) ;
$ ( '#recording-status' ) . text ( "Make a Recording" ) ;
}
function displayWhoCreated ( clientId ) {
if ( app . clientId != clientId ) { // don't show to creator
sessionModel . findUserBy ( { clientId : clientId } )
. done ( function ( user ) {
app . notify ( {
"title" : "Recording Started" ,
"text" : user . name + " started a recording" ,
"icon_url" : context . JK . resolveAvatarUrl ( user . photo _url )
} ) ;
} )
. fail ( function ( ) {
app . notify ( {
"title" : "Recording Started" ,
"text" : "Oops! Can't determine who started this recording" ,
"icon_url" : "/assets/content/icon_alert_big.png"
} ) ;
} )
}
}
function promptUserToSave ( recordingId ) {
rest . getRecording ( { id : recordingId } )
. done ( function ( recording ) {
2014-01-06 20:35:35 +00:00
recordingFinishedDialog . setRecording ( recording ) ;
2013-11-03 20:55:55 +00:00
app . layout . showDialog ( 'recordingFinished' ) ;
} )
. fail ( app . ajaxError ) ;
}
2014-01-05 03:47:23 +00:00
function openRecording ( e ) {
// just ignore the click if they are currently recording for now
if ( sessionModel . recordingModel . isRecording ( ) ) {
app . notify ( {
"title" : "Currently Recording" ,
"text" : "You can't open a recording while creating a recording." ,
"icon_url" : "/assets/content/icon_alert_big.png"
} ) ;
return false ;
}
if ( ! localRecordingsDialog . isShowing ( ) ) {
app . layout . showDialog ( 'localRecordings' ) ;
}
return false ;
}
function closeRecording ( ) {
rest . stopPlayClaimedRecording ( { id : sessionModel . id ( ) , claimed _recording _id : sessionModel . getCurrentSession ( ) . claimed _recording . id } )
. done ( function ( ) {
sessionModel . refreshCurrentSession ( ) ;
} )
. fail ( function ( jqXHR ) {
app . notify ( {
"title" : "Couldn't Stop Recording Playback" ,
"text" : "Couldn't inform the server to stop playback. msg=" + jqXHR . responseText ,
"icon_url" : "/assets/content/icon_alert_big.png"
} ) ;
} ) ;
context . jamClient . CloseRecording ( ) ;
return false ;
}
function onPause ( ) {
logger . debug ( "calling jamClient.SessionStopPlay" ) ;
context . jamClient . SessionStopPlay ( ) ;
}
2014-01-06 20:35:35 +00:00
function onPlay ( e , data ) {
2014-01-05 03:47:23 +00:00
logger . debug ( "calling jamClient.SessionStartPlay" ) ;
2014-01-06 20:35:35 +00:00
context . jamClient . SessionStartPlay ( data . playbackMode ) ;
2014-01-05 03:47:23 +00:00
}
function onChangePlayPosition ( e , data ) {
logger . debug ( "calling jamClient.SessionTrackSeekMs(" + data . positionMs + ")" ) ;
context . jamClient . SessionTrackSeekMs ( data . positionMs ) ;
}
2013-11-03 20:55:55 +00:00
function startStopRecording ( ) {
if ( sessionModel . recordingModel . isRecording ( ) ) {
sessionModel . recordingModel . stopRecording ( ) ;
}
else {
sessionModel . recordingModel . startRecording ( ) ;
}
}
2014-01-14 01:00:42 +00:00
function inviteMusicians ( ) {
2014-01-14 06:12:00 +00:00
inviteMusiciansUtil . inviteSessionUpdate ( '#update-session-invite-musicians' , sessionId ) ;
2014-01-14 01:00:42 +00:00
}
2012-10-06 16:36:05 +00:00
function events ( ) {
2014-02-14 23:41:01 +00:00
$ ( '#session-resync' ) . on ( 'click' , sessionResync ) ;
2012-10-06 16:36:05 +00:00
$ ( '#session-contents' ) . on ( "click" , '[action="delete"]' , deleteSession ) ;
2013-01-31 16:32:32 +00:00
$ ( '#tracks' ) . on ( 'click' , 'div[control="mute"]' , toggleMute ) ;
2014-01-05 03:47:23 +00:00
$ ( '#recording-start-stop' ) . on ( 'click' , startStopRecording ) ;
$ ( '#open-a-recording' ) . on ( 'click' , openRecording ) ;
2014-01-14 01:00:42 +00:00
$ ( '#session-invite-musicians' ) . on ( 'click' , inviteMusicians ) ;
2013-10-26 01:34:40 +00:00
$ ( '#track-settings' ) . click ( function ( ) {
2013-05-22 05:03:34 +00:00
configureTrackDialog . showVoiceChatPanel ( true ) ;
2013-10-26 01:34:40 +00:00
configureTrackDialog . showMusicAudioPanel ( true ) ;
2013-05-18 05:59:25 +00:00
} ) ;
2014-01-25 15:37:15 +00:00
2014-01-05 03:47:23 +00:00
$ ( '#close-playback-recording' ) . on ( 'click' , closeRecording ) ;
$ ( playbackControls )
. on ( 'pause' , onPause )
. on ( 'play' , onPlay )
. on ( 'change-position' , onChangePlayPosition ) ;
2012-10-06 16:36:05 +00:00
}
2014-01-14 06:12:00 +00:00
this . initialize = function ( localRecordingsDialogInstance , recordingFinishedDialogInstance , inviteMusiciansUtilInstance ) {
2014-01-05 03:47:23 +00:00
localRecordingsDialog = localRecordingsDialogInstance ;
2014-01-06 20:35:35 +00:00
recordingFinishedDialog = recordingFinishedDialogInstance ;
2014-01-14 06:12:00 +00:00
inviteMusiciansUtil = inviteMusiciansUtilInstance ;
2013-02-15 05:15:36 +00:00
context . jamClient . SetVURefreshRate ( 150 ) ;
2014-02-13 18:54:54 +00:00
context . jamClient . RegisterVolChangeCallBack ( "JK.HandleVolumeChangeCallback" ) ;
2014-01-05 03:47:23 +00:00
playbackControls = new context . JK . PlaybackControls ( $ ( '.session-recordings .recording-controls' ) ) ;
2012-10-14 15:48:56 +00:00
events ( ) ;
2014-01-05 03:47:23 +00:00
2012-12-07 00:28:48 +00:00
var screenBindings = {
2012-10-29 15:10:02 +00:00
'beforeShow' : beforeShow ,
2012-10-29 00:37:59 +00:00
'afterShow' : afterShow ,
2012-10-29 15:10:02 +00:00
'beforeHide' : beforeHide
2012-10-14 15:48:56 +00:00
} ;
app . bindScreen ( 'session' , screenBindings ) ;
2012-10-06 16:36:05 +00:00
} ;
2013-10-12 22:35:12 +00:00
2012-10-22 02:55:05 +00:00
this . tracks = tracks ;
2013-06-10 02:12:43 +00:00
this . getCurrentSession = function ( ) {
return sessionModel . getCurrentSession ( ) ;
2013-06-09 17:02:53 +00:00
} ;
2013-06-11 01:53:49 +00:00
this . refreshCurrentSession = function ( ) {
sessionModel . refreshCurrentSession ( ) ;
} ;
2013-08-07 20:04:22 +00:00
context . JK . HandleVolumeChangeCallback = handleVolumeChangeCallback ;
2013-01-30 16:50:43 +00:00
context . JK . HandleBridgeCallback = handleBridgeCallback ;
2013-07-09 03:06:01 +00:00
context . JK . AlertCallback = alertCallback ;
2012-10-06 16:36:05 +00:00
} ;
} ) ( window , jQuery ) ;