stable stopping point with initial webrtc attempts
This commit is contained in:
parent
983c690451
commit
61a7ed223b
|
|
@ -232,3 +232,8 @@ Determine whether frontend code can support `await context.jamClient.method()` f
|
||||||
- `session_utils.js` join-source trace now writes to buffer (`join-source.session-utils.joinSession`) while downgrading console severity.
|
- `session_utils.js` join-source trace now writes to buffer (`join-source.session-utils.joinSession`) while downgrading console severity.
|
||||||
- Added a hard gate in `SessionStoreModern.es6` so the modern Reflux store only installs when `window.__JK_SKIP_LEGACY_SESSION_STORE__` is true.
|
- Added a hard gate in `SessionStoreModern.es6` so the modern Reflux store only installs when `window.__JK_SKIP_LEGACY_SESSION_STORE__` is true.
|
||||||
- In legacy bundle loads, modern store now emits `join-source.session-store.modern-skipped` and does not register listeners; this prevents hidden second `onJoinSession` listeners from racing the legacy store.
|
- In legacy bundle loads, modern store now emits `join-source.session-store.modern-skipped` and does not register listeners; this prevents hidden second `onJoinSession` listeners from racing the legacy store.
|
||||||
|
- Added targeted leave-source instrumentation to diagnose immediate post-join session departure:
|
||||||
|
- `SessionActions.leaveSession` now emits `leave-source.session-action` (call + trigger wrappers, with stack) in `react-components/actions/SessionActions.js.coffee`.
|
||||||
|
- `SessionStore.js.coffee#onLeaveSession` now emits `leave-source.session-store.legacy-onLeaveSession`.
|
||||||
|
- `SessionStoreModern.es6#onLeaveSession` now emits `leave-source.session-store.modern-onLeaveSession`.
|
||||||
|
- This is intended to identify the exact caller path behind immediate `DELETE /api/participants/:client_id` after successful join.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ Build a web-client-backed `jamClient` compatibility path plus a simulator/test h
|
||||||
- [x] Create task tracking and record the initial plan
|
- [x] Create task tracking and record the initial plan
|
||||||
- [ ] Complete async bridge contract spike (`await context.jamClient.method()` viability and blast radius)
|
- [ ] Complete async bridge contract spike (`await context.jamClient.method()` viability and blast radius)
|
||||||
- [ ] Complete async bridge contract spike (`await context.jamClient.method()` viability and blast radius) (dual-bundle parser split + `SessionStore` legacy/modern split in progress)
|
- [ ] Complete async bridge contract spike (`await context.jamClient.method()` viability and blast radius) (dual-bundle parser split + `SessionStore` legacy/modern split in progress)
|
||||||
- [ ] Map protocol surface (jamClient methods, backend callbacks, REST APIs, websocket messages, shared-object usage)
|
- [x] Map protocol surface for join/mute/volume flows using native 2-party recordings
|
||||||
- [ ] Add instrumentation/tape-recording hooks (frontend/backend)
|
- [ ] Add instrumentation/tape-recording hooks (frontend/backend)
|
||||||
- [ ] Define comparison assertions for compatibility tests
|
- [ ] Define comparison assertions for compatibility tests
|
||||||
- [ ] Implement web `jamClient` shim compatibility layer
|
- [ ] Implement web `jamClient` shim compatibility layer
|
||||||
|
|
@ -15,6 +15,15 @@ Build a web-client-backed `jamClient` compatibility path plus a simulator/test h
|
||||||
- [ ] Build MVP web-to-web session flow
|
- [ ] Build MVP web-to-web session flow
|
||||||
- [ ] Build simulator/replay harness and automated specs
|
- [ ] Build simulator/replay harness and automated specs
|
||||||
|
|
||||||
|
## 2026-03-01 WebRTC Planning Update
|
||||||
|
- Captured native reference logs for 2-party mute/volume scenario in:
|
||||||
|
- `web/ai/native-client-2p-recording/20260228-210957-seth-native-2p-mute-volume.log`
|
||||||
|
- `web/ai/native-client-2p-recording/20260228-211006-david-native-2p-mute-volume.log`
|
||||||
|
- Confirmed these are sufficient as baseline artifacts for bridge-call and REST/websocket pattern parity while implementing web-client media with selective behavioral divergence.
|
||||||
|
- Added execution plan in `agent-tasks/client-simulator/webrtc-web-client-plan/progress.md`.
|
||||||
|
- Added Tier A/B/C contract matrix from the captured logs in:
|
||||||
|
- `web/ai/native-client-2p-recording/contract-matrix.md`
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
- Native recording execution is intentionally out of scope for this effort (native path is being deprecated).
|
- Native recording execution is intentionally out of scope for this effort (native path is being deprecated).
|
||||||
- Recording/comparison scope must include REST API requests/responses in addition to bridge/events/websocket/DB behavior.
|
- Recording/comparison scope must include REST API requests/responses in addition to bridge/events/websocket/DB behavior.
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@
|
||||||
//= require AAA_Log
|
//= require AAA_Log
|
||||||
//= require globals
|
//= require globals
|
||||||
//= require debug_log_collector
|
//= require debug_log_collector
|
||||||
|
//= require mode_flags_overlay
|
||||||
//= require AAB_message_factory
|
//= require AAB_message_factory
|
||||||
//= require jam_rest
|
//= require jam_rest
|
||||||
//= require ga
|
//= require ga
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@
|
||||||
//= require AAA_Log
|
//= require AAA_Log
|
||||||
//= require globals
|
//= require globals
|
||||||
//= require debug_log_collector
|
//= require debug_log_collector
|
||||||
|
//= require mode_flags_overlay
|
||||||
//= require AAB_message_factory
|
//= require AAB_message_factory
|
||||||
//= require jam_rest
|
//= require jam_rest
|
||||||
//= require ga
|
//= require ga
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,26 @@
|
||||||
$(this).removeClass('hover');
|
$(this).removeClass('hover');
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchClientMode(e) {
|
function setWebClientWebRtcFlag(enabled) {
|
||||||
// ctrl + shift + 0
|
try {
|
||||||
if(e.ctrlKey && e.shiftKey && e.keyCode == 48) {
|
if (!window.localStorage) { return; }
|
||||||
logger.debug("switch client mode!");
|
if (enabled) {
|
||||||
var act_as_native_client = $.cookie('act_as_native_client');
|
window.localStorage.setItem('jk.webClient.webrtc', '1');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.localStorage.removeItem('jk.webClient.webrtc');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
|
logger.warn("unable to update jk.webClient.webrtc localStorage flag", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug("currently: " + act_as_native_client);
|
function toggleActAsNativeClient() {
|
||||||
if(act_as_native_client == null || act_as_native_client != "true") {
|
var act_as_native_client = $.cookie('act_as_native_client');
|
||||||
|
var enable = (act_as_native_client == null || act_as_native_client != "true");
|
||||||
|
|
||||||
|
if(enable) {
|
||||||
logger.debug("forcing act as native client!");
|
logger.debug("forcing act as native client!");
|
||||||
$.cookie('act_as_native_client', 'true', { expires: 120, path: '/' });
|
$.cookie('act_as_native_client', 'true', { expires: 120, path: '/' });
|
||||||
}
|
}
|
||||||
|
|
@ -43,6 +55,29 @@
|
||||||
logger.debug("remove act as native client!");
|
logger.debug("remove act as native client!");
|
||||||
$.removeCookie('act_as_native_client');
|
$.removeCookie('act_as_native_client');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchClientMode(e) {
|
||||||
|
if(!(e.ctrlKey && e.shiftKey)) { return; }
|
||||||
|
|
||||||
|
// ctrl + shift + 0
|
||||||
|
if(e.keyCode == 48) {
|
||||||
|
logger.debug("switch client mode!");
|
||||||
|
toggleActAsNativeClient();
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ctrl + shift + 9
|
||||||
|
// One-step toggle for web client mode:
|
||||||
|
// - act_as_native_client cookie
|
||||||
|
// - localStorage flag enabling WebRTC path in WebJamClient
|
||||||
|
if(e.keyCode == 57) {
|
||||||
|
logger.debug("switch web client mode (native-cookie + webrtc flag)!");
|
||||||
|
var enabled = toggleActAsNativeClient();
|
||||||
|
setWebClientWebRtcFlag(enabled);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -594,6 +594,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteParticipant(clientId) {
|
function deleteParticipant(clientId) {
|
||||||
|
if (context.JK && context.JK.DebugLogCollector && context.JK.DebugLogCollector.push) {
|
||||||
|
context.JK.DebugLogCollector.push("leave-source.rest.deleteParticipant", {
|
||||||
|
client_id: clientId,
|
||||||
|
stack: (new Error("jam_rest.deleteParticipant")).stack
|
||||||
|
});
|
||||||
|
}
|
||||||
var url = "/api/participants/" + clientId;
|
var url = "/api/participants/" + clientId;
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
type: "DELETE",
|
type: "DELETE",
|
||||||
|
|
|
||||||
|
|
@ -51,3 +51,29 @@ if @SessionActions?.joinSession?
|
||||||
originalTrigger.apply(originalJoinAction, arguments)
|
originalTrigger.apply(originalJoinAction, arguments)
|
||||||
|
|
||||||
@SessionActions.joinSession = wrappedJoinAction
|
@SessionActions.joinSession = wrappedJoinAction
|
||||||
|
|
||||||
|
pushLeaveActionTrace = (source, argsLike) ->
|
||||||
|
return unless context.JK?.DebugLogCollector?.push
|
||||||
|
args = Array::slice.call(argsLike ? [])
|
||||||
|
context.JK.DebugLogCollector.push("leave-source.session-action", {
|
||||||
|
source: source
|
||||||
|
args: args
|
||||||
|
stack: (new Error("SessionActions.leaveSession")).stack
|
||||||
|
})
|
||||||
|
|
||||||
|
if @SessionActions?.leaveSession?
|
||||||
|
originalLeaveAction = @SessionActions.leaveSession
|
||||||
|
wrappedLeaveAction = ->
|
||||||
|
pushLeaveActionTrace('call', arguments)
|
||||||
|
originalLeaveAction.apply(this, arguments)
|
||||||
|
|
||||||
|
for own key, value of originalLeaveAction
|
||||||
|
wrappedLeaveAction[key] = value
|
||||||
|
|
||||||
|
if originalLeaveAction.trigger?
|
||||||
|
originalLeaveTrigger = originalLeaveAction.trigger
|
||||||
|
wrappedLeaveAction.trigger = ->
|
||||||
|
pushLeaveActionTrace('trigger', arguments)
|
||||||
|
originalLeaveTrigger.apply(originalLeaveAction, arguments)
|
||||||
|
|
||||||
|
@SessionActions.leaveSession = wrappedLeaveAction
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ MIDI_TRACK = context.JK.MIDI_TRACK
|
||||||
|
|
||||||
# find any VST info
|
# find any VST info
|
||||||
if hasMixer && @configureTracks?
|
if hasMixer && @configureTracks?
|
||||||
|
vstAssignments = @configureTracks?.vstTrackAssignments?.vsts ? []
|
||||||
|
|
||||||
# bug in the backend; track is wrong for personal mixers (always 1), but correct for master mix
|
# bug in the backend; track is wrong for personal mixers (always 1), but correct for master mix
|
||||||
trackAssignment = -1
|
trackAssignment = -1
|
||||||
|
|
@ -79,7 +80,7 @@ MIDI_TRACK = context.JK.MIDI_TRACK
|
||||||
else
|
else
|
||||||
trackAssignment = mixerData.oppositeMixer?.track
|
trackAssignment = mixerData.oppositeMixer?.track
|
||||||
|
|
||||||
for vst in @configureTracks.vstTrackAssignments.vsts
|
for vst in vstAssignments
|
||||||
if vst.track == trackAssignment - 1 && vst.name != 'NONE'
|
if vst.track == trackAssignment - 1 && vst.name != 'NONE'
|
||||||
logger.debug("found VST on track", vst, track)
|
logger.debug("found VST on track", vst, track)
|
||||||
associatedVst = vst
|
associatedVst = vst
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,8 @@ void removeSearchPath(int typeId, QString pathToRemove);
|
||||||
# the backend does not have a consistent way of tracking assigned inputs for midi.
|
# the backend does not have a consistent way of tracking assigned inputs for midi.
|
||||||
# let's make it seem consistent
|
# let's make it seem consistent
|
||||||
injectMidiToTrackAssignments: () ->
|
injectMidiToTrackAssignments: () ->
|
||||||
|
return unless @trackAssignments?.inputs?.assigned?
|
||||||
|
return unless @vstTrackAssignments?.vsts?
|
||||||
if @vstTrackAssignments?
|
if @vstTrackAssignments?
|
||||||
for vst in @vstTrackAssignments.vsts
|
for vst in @vstTrackAssignments.vsts
|
||||||
if vst.track == MIDI_TRACK - 1
|
if vst.track == MIDI_TRACK - 1
|
||||||
|
|
@ -263,26 +265,28 @@ void removeSearchPath(int typeId, QString pathToRemove);
|
||||||
changed: () ->
|
changed: () ->
|
||||||
|
|
||||||
@injectMidiToTrackAssignments()
|
@injectMidiToTrackAssignments()
|
||||||
|
assignedInputs = @trackAssignments?.inputs?.assigned ? []
|
||||||
|
vstAssignments = @vstTrackAssignments?.vsts ? []
|
||||||
|
|
||||||
@editingTrack = []
|
@editingTrack = []
|
||||||
@editingTrack.assignment = @trackNumber
|
@editingTrack.assignment = @trackNumber
|
||||||
|
|
||||||
if @trackNumber?
|
if @trackNumber?
|
||||||
|
|
||||||
for inputsForTrack in @trackAssignments.inputs.assigned
|
for inputsForTrack in assignedInputs
|
||||||
if inputsForTrack.assignment == @trackNumber
|
if inputsForTrack.assignment == @trackNumber
|
||||||
@editingTrack = inputsForTrack
|
@editingTrack = inputsForTrack
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
# slap on vst, if any, from list of vst assignments
|
# slap on vst, if any, from list of vst assignments
|
||||||
for vst in @vstTrackAssignments.vsts
|
for vst in vstAssignments
|
||||||
if vst.track == @editingTrack.assignment - 1
|
if vst.track == @editingTrack.assignment - 1
|
||||||
@editingTrack.vst = vst
|
@editingTrack.vst = vst
|
||||||
@editingTrack.midiDeviceIndex = vst.midiDeviceIndex
|
@editingTrack.midiDeviceIndex = vst.midiDeviceIndex
|
||||||
break
|
break
|
||||||
|
|
||||||
for inputsForTrack in @trackAssignments.inputs.assigned
|
for inputsForTrack in assignedInputs
|
||||||
if vst.track == inputsForTrack.assignment - 1
|
if vst.track == inputsForTrack.assignment - 1
|
||||||
inputsForTrack.vst = vst
|
inputsForTrack.vst = vst
|
||||||
|
|
||||||
|
|
@ -290,7 +294,7 @@ void removeSearchPath(int typeId, QString pathToRemove);
|
||||||
logger.debug("current track has a VST assigned:" + @editingTrack.vst.file)
|
logger.debug("current track has a VST assigned:" + @editingTrack.vst.file)
|
||||||
|
|
||||||
|
|
||||||
unscanned = !@scannedBefore && @vstPluginList.vsts.length <= 1
|
unscanned = !@scannedBefore && ((@vstPluginList?.vsts?.length ? 0) <= 1)
|
||||||
|
|
||||||
@item = {
|
@item = {
|
||||||
unscanned: unscanned,
|
unscanned: unscanned,
|
||||||
|
|
|
||||||
|
|
@ -1169,6 +1169,12 @@ if context.JK?.DebugLogCollector?.push
|
||||||
# called by anyone wanting to leave the session with a certain behavior
|
# called by anyone wanting to leave the session with a certain behavior
|
||||||
onLeaveSession: (behavior) ->
|
onLeaveSession: (behavior) ->
|
||||||
logger.debug("attempting to leave session", behavior)
|
logger.debug("attempting to leave session", behavior)
|
||||||
|
if context.JK?.DebugLogCollector?.push
|
||||||
|
context.JK.DebugLogCollector.push("leave-source.session-store.legacy-onLeaveSession", {
|
||||||
|
behavior: behavior
|
||||||
|
current_session_id: @currentSessionId
|
||||||
|
stack: (new Error("SessionStoreLegacy.onLeaveSession")).stack
|
||||||
|
})
|
||||||
|
|
||||||
if behavior.notify
|
if behavior.notify
|
||||||
@app.layout.notify(behavior.notify)
|
@app.layout.notify(behavior.notify)
|
||||||
|
|
|
||||||
|
|
@ -809,6 +809,23 @@ if (shouldInstallModernSessionStore) {
|
||||||
},
|
},
|
||||||
|
|
||||||
async onJoinSessionDone(musicSession) {
|
async onJoinSessionDone(musicSession) {
|
||||||
|
const pushJoinAbort = (reason, detail) => {
|
||||||
|
context.__jkJoinAborts = context.__jkJoinAborts || [];
|
||||||
|
context.__jkJoinAborts.push({
|
||||||
|
ts: (new Date()).toISOString(),
|
||||||
|
reason,
|
||||||
|
detail,
|
||||||
|
current_session_id: this.currentSessionId
|
||||||
|
});
|
||||||
|
if (context.JK && context.JK.DebugLogCollector && context.JK.DebugLogCollector.push) {
|
||||||
|
context.JK.DebugLogCollector.push("join-source.session-store.modern-abort", {
|
||||||
|
reason,
|
||||||
|
detail,
|
||||||
|
current_session_id: this.currentSessionId,
|
||||||
|
stack: (new Error("SessionStoreModern.onJoinSessionDone.abort")).stack
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
const musicianAccessOnJoin = musicSession.musician_access;
|
const musicianAccessOnJoin = musicSession.musician_access;
|
||||||
const shouldVerifyNetwork = musicSession.musician_access;
|
const shouldVerifyNetwork = musicSession.musician_access;
|
||||||
|
|
||||||
|
|
@ -827,6 +844,7 @@ if (shouldInstallModernSessionStore) {
|
||||||
try {
|
try {
|
||||||
await this.gearUtils.guardAgainstInvalidConfiguration(this.app, shouldVerifyNetwork);
|
await this.gearUtils.guardAgainstInvalidConfiguration(this.app, shouldVerifyNetwork);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
pushJoinAbort("guardAgainstInvalidConfiguration", {error: String(e)});
|
||||||
SessionActions.leaveSession.trigger({location: '/client#/home'});
|
SessionActions.leaveSession.trigger({location: '/client#/home'});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -848,6 +866,7 @@ if (shouldInstallModernSessionStore) {
|
||||||
leaveBehavior.location = '/client#/home';
|
leaveBehavior.location = '/client#/home';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushJoinAbort("guardAgainstActiveProfileMissing", data);
|
||||||
SessionActions.leaveSession.trigger(leaveBehavior);
|
SessionActions.leaveSession.trigger(leaveBehavior);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -864,6 +883,7 @@ if (shouldInstallModernSessionStore) {
|
||||||
context.JK.alertSupportedNeeded('Unable to determine configured tracks due to reason: ' + data);
|
context.JK.alertSupportedNeeded('Unable to determine configured tracks due to reason: ' + data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushJoinAbort("waitForSessionPageEnterDone", {error: data});
|
||||||
SessionActions.leaveSession.trigger({location: '/client#/home'});
|
SessionActions.leaveSession.trigger({location: '/client#/home'});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1250,6 +1270,13 @@ if (shouldInstallModernSessionStore) {
|
||||||
|
|
||||||
|
|
||||||
leaveSessionRest() {
|
leaveSessionRest() {
|
||||||
|
if (context.JK && context.JK.DebugLogCollector && context.JK.DebugLogCollector.push) {
|
||||||
|
context.JK.DebugLogCollector.push("leave-source.session-store.modern-leaveSessionRest", {
|
||||||
|
current_session_id: this.currentSessionId,
|
||||||
|
client_id: this.app && this.app.clientId,
|
||||||
|
stack: (new Error("SessionStoreModern.leaveSessionRest")).stack
|
||||||
|
});
|
||||||
|
}
|
||||||
return rest.deleteParticipant(this.app.clientId);
|
return rest.deleteParticipant(this.app.clientId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1436,6 +1463,13 @@ if (shouldInstallModernSessionStore) {
|
||||||
// called by anyone wanting to leave the session with a certain behavior
|
// called by anyone wanting to leave the session with a certain behavior
|
||||||
onLeaveSession(behavior) {
|
onLeaveSession(behavior) {
|
||||||
logger.debug("attempting to leave session", behavior);
|
logger.debug("attempting to leave session", behavior);
|
||||||
|
if (context.JK && context.JK.DebugLogCollector && context.JK.DebugLogCollector.push) {
|
||||||
|
context.JK.DebugLogCollector.push("leave-source.session-store.modern-onLeaveSession", {
|
||||||
|
behavior: behavior,
|
||||||
|
current_session_id: this.currentSessionId,
|
||||||
|
stack: (new Error("SessionStoreModern.onLeaveSession")).stack
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (behavior.notify) {
|
if (behavior.notify) {
|
||||||
this.app.layout.notify(behavior.notify);
|
this.app.layout.notify(behavior.notify);
|
||||||
|
|
|
||||||
|
|
@ -631,6 +631,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function leaveSessionRest(sessionId) {
|
function leaveSessionRest(sessionId) {
|
||||||
|
if (context.JK && context.JK.DebugLogCollector && context.JK.DebugLogCollector.push) {
|
||||||
|
context.JK.DebugLogCollector.push("leave-source.session-model.leaveSessionRest", {
|
||||||
|
session_id: sessionId,
|
||||||
|
client_id: clientId,
|
||||||
|
stack: (new Error("SessionModel.leaveSessionRest")).stack
|
||||||
|
});
|
||||||
|
}
|
||||||
var url = "/api/participants/" + clientId;
|
var url = "/api/participants/" + clientId;
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
type: "DELETE",
|
type: "DELETE",
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,20 @@
|
||||||
stream: null,
|
stream: null,
|
||||||
error: null,
|
error: null,
|
||||||
autoStartEnabled: false
|
autoStartEnabled: false
|
||||||
|
},
|
||||||
|
controlState: {
|
||||||
|
master: {},
|
||||||
|
personal: {},
|
||||||
|
initialized: {
|
||||||
|
master: false,
|
||||||
|
personal: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
participants: {},
|
||||||
|
webrtc: {
|
||||||
|
enabled: false,
|
||||||
|
peerConnections: {},
|
||||||
|
localClientId: null
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -228,6 +242,352 @@
|
||||||
pushRecord({kind: "webClientMediaStatus", status: state.localMedia.status});
|
pushRecord({kind: "webClientMediaStatus", status: state.localMedia.status});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureLocalMediaForWebRtc() {
|
||||||
|
if (!state.webrtc.enabled) { return; }
|
||||||
|
if (!context.navigator || !context.navigator.mediaDevices || !context.navigator.mediaDevices.getUserMedia) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.localMedia.status === "requesting" || state.localMedia.status === "active") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.localMedia.status = "requesting";
|
||||||
|
state.localMedia.error = null;
|
||||||
|
pushRecord({kind: "webClientMediaStatus", status: state.localMedia.status, reason: "webrtc"});
|
||||||
|
|
||||||
|
context.navigator.mediaDevices.getUserMedia({audio: true, video: false})
|
||||||
|
.then(function(stream) {
|
||||||
|
state.localMedia.stream = stream;
|
||||||
|
state.localMedia.status = "active";
|
||||||
|
state.localMedia.error = null;
|
||||||
|
pushRecord({
|
||||||
|
kind: "webClientMediaStatus",
|
||||||
|
status: state.localMedia.status,
|
||||||
|
reason: "webrtc",
|
||||||
|
tracks: stream && stream.getAudioTracks ? stream.getAudioTracks().length : 0
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(state.webrtc.peerConnections).forEach(function(clientId) {
|
||||||
|
var entry = state.webrtc.peerConnections[clientId];
|
||||||
|
if (entry && entry.pc) {
|
||||||
|
addLocalTracksToPeerConnection(entry.pc);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
state.localMedia.stream = null;
|
||||||
|
state.localMedia.status = "failed";
|
||||||
|
state.localMedia.error = (err && err.name) ? err.name : String(err);
|
||||||
|
pushRecord({
|
||||||
|
kind: "webClientMediaStatus",
|
||||||
|
status: state.localMedia.status,
|
||||||
|
reason: "webrtc",
|
||||||
|
error: state.localMedia.error
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function modeKey(isMasterOrPersonal) {
|
||||||
|
return isMasterOrPersonal ? "master" : "personal";
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepClone(value) {
|
||||||
|
return safeSerialize(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getJamServer() {
|
||||||
|
return context.JK && context.JK.JamServer ? context.JK.JamServer : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelfClientId() {
|
||||||
|
if (state.webrtc.localClientId) { return state.webrtc.localClientId; }
|
||||||
|
if (state.app && state.app.clientId) { return state.app.clientId; }
|
||||||
|
var server = getJamServer();
|
||||||
|
if (server && server.clientID) { return server.clientID; }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readWebRtcEnabledFlag() {
|
||||||
|
return !!context.JK_WEB_CLIENT_WEBRTC_ENABLED ||
|
||||||
|
readLocalStorageFlag("jk.webClient.webrtc") ||
|
||||||
|
!!(context.gon && context.gon.web_client_webrtc_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureControlStateCache(isMasterOrPersonal) {
|
||||||
|
var key = modeKey(isMasterOrPersonal);
|
||||||
|
if (state.controlState.initialized[key]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var delegateState = invokeDelegate("SessionGetAllControlState", [isMasterOrPersonal]);
|
||||||
|
var byId = {};
|
||||||
|
if (delegateState && delegateState.length) {
|
||||||
|
delegateState.forEach(function(mixer) {
|
||||||
|
if (!mixer || !mixer.id) { return; }
|
||||||
|
byId[mixer.id] = deepClone(mixer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
state.controlState[key] = byId;
|
||||||
|
state.controlState.initialized[key] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var syncTracksTimer = null;
|
||||||
|
function scheduleTrackSync() {
|
||||||
|
if (!context.MixerActions || !context.MixerActions.syncTracks) { return; }
|
||||||
|
if (syncTracksTimer) {
|
||||||
|
context.clearTimeout(syncTracksTimer);
|
||||||
|
}
|
||||||
|
syncTracksTimer = context.setTimeout(function() {
|
||||||
|
syncTracksTimer = null;
|
||||||
|
try {
|
||||||
|
context.MixerActions.syncTracks();
|
||||||
|
pushRecord({kind: "webClientTrackSync", source: "SessionSetControlState"});
|
||||||
|
} catch (e) {
|
||||||
|
pushRecord({
|
||||||
|
kind: "webClientTrackSyncError",
|
||||||
|
error: (e && e.message) ? e.message : String(e)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTrackVolumeToCache(mixerId, isMasterOrPersonal) {
|
||||||
|
ensureControlStateCache(isMasterOrPersonal);
|
||||||
|
var key = modeKey(isMasterOrPersonal);
|
||||||
|
var cache = state.controlState[key];
|
||||||
|
var existing = cache[mixerId] || {};
|
||||||
|
var trackVolume = context.trackVolumeObject || {};
|
||||||
|
|
||||||
|
var updated = deepClone(existing) || {};
|
||||||
|
updated.id = mixerId;
|
||||||
|
updated.master = !!isMasterOrPersonal;
|
||||||
|
updated.monitor = !isMasterOrPersonal;
|
||||||
|
if (trackVolume.clientID !== undefined) { updated.client_id = trackVolume.clientID; }
|
||||||
|
if (trackVolume.mute !== undefined) { updated.mute = !!trackVolume.mute; }
|
||||||
|
if (trackVolume.volL !== undefined) { updated.volume_left = trackVolume.volL; }
|
||||||
|
if (trackVolume.volR !== undefined) { updated.volume_right = trackVolume.volR; }
|
||||||
|
if (trackVolume.pan !== undefined) { updated.pan = trackVolume.pan; }
|
||||||
|
if (trackVolume.loop !== undefined) { updated.loop = !!trackVolume.loop; }
|
||||||
|
if (trackVolume.name !== undefined) { updated.name = trackVolume.name; }
|
||||||
|
if (trackVolume.record !== undefined) { updated.record = !!trackVolume.record; }
|
||||||
|
if (updated.range_low === undefined) { updated.range_low = -80; }
|
||||||
|
if (updated.range_high === undefined) { updated.range_high = 20; }
|
||||||
|
|
||||||
|
cache[mixerId] = updated;
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLocalTrackMuteFromControlState(controlState) {
|
||||||
|
if (!controlState) { return; }
|
||||||
|
if (!state.localMedia.stream || !state.localMedia.stream.getAudioTracks) { return; }
|
||||||
|
|
||||||
|
var trackClientId = controlState.client_id || "";
|
||||||
|
var selfClientId = getSelfClientId();
|
||||||
|
var isSelfControl = !trackClientId || (selfClientId && trackClientId === selfClientId);
|
||||||
|
if (!isSelfControl) { return; }
|
||||||
|
|
||||||
|
var shouldMute = !!controlState.mute;
|
||||||
|
state.localMedia.stream.getAudioTracks().forEach(function(track) {
|
||||||
|
try {
|
||||||
|
track.enabled = !shouldMute;
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPeerConnectionCtor() {
|
||||||
|
return context.RTCPeerConnection || context.webkitRTCPeerConnection || context.mozRTCPeerConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendWebRtcSignal(receiverClientId, payload) {
|
||||||
|
var server = getJamServer();
|
||||||
|
if (!server || typeof server.sendP2PMessage !== "function") {
|
||||||
|
pushRecord({kind: "webrtc.signalDropped", reason: "jamServerUnavailable", to: receiverClientId});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var envelope = {
|
||||||
|
jk_webclient_webrtc: true,
|
||||||
|
session_id: state.currentSessionId,
|
||||||
|
from_client_id: getSelfClientId(),
|
||||||
|
payload: payload
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
server.sendP2PMessage(receiverClientId, JSON.stringify(envelope));
|
||||||
|
pushRecord({
|
||||||
|
kind: "webrtc.signalSent",
|
||||||
|
to: receiverClientId,
|
||||||
|
signal_type: payload && payload.type ? payload.type : "candidate"
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
pushRecord({
|
||||||
|
kind: "webrtc.signalSendError",
|
||||||
|
to: receiverClientId,
|
||||||
|
error: (e && e.message) ? e.message : String(e)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLocalTracksToPeerConnection(peerConnection) {
|
||||||
|
if (!peerConnection || !state.localMedia.stream || !state.localMedia.stream.getTracks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = state.localMedia.stream;
|
||||||
|
stream.getTracks().forEach(function(track) {
|
||||||
|
try {
|
||||||
|
peerConnection.addTrack(track, stream);
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePeerConnection(clientId) {
|
||||||
|
var entry = state.webrtc.peerConnections[clientId];
|
||||||
|
if (!entry) { return; }
|
||||||
|
try {
|
||||||
|
if (entry.pc && typeof entry.pc.close === "function") {
|
||||||
|
entry.pc.close();
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
delete state.webrtc.peerConnections[clientId];
|
||||||
|
pushRecord({kind: "webrtc.peerClosed", client_id: clientId});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensurePeerConnection(clientId) {
|
||||||
|
if (!state.webrtc.enabled || !clientId) { return null; }
|
||||||
|
if (state.webrtc.peerConnections[clientId]) {
|
||||||
|
return state.webrtc.peerConnections[clientId];
|
||||||
|
}
|
||||||
|
|
||||||
|
var RTCPeerConnectionCtor = getPeerConnectionCtor();
|
||||||
|
if (!RTCPeerConnectionCtor) {
|
||||||
|
pushRecord({kind: "webrtc.unavailable", reason: "RTCPeerConnection"});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var iceServers = (context.gon && context.gon.web_client_ice_servers) || [];
|
||||||
|
var peerConnection = null;
|
||||||
|
try {
|
||||||
|
peerConnection = new RTCPeerConnectionCtor({iceServers: iceServers});
|
||||||
|
} catch (e) {
|
||||||
|
pushRecord({
|
||||||
|
kind: "webrtc.peerCreateError",
|
||||||
|
client_id: clientId,
|
||||||
|
error: (e && e.message) ? e.message : String(e)
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = {
|
||||||
|
client_id: clientId,
|
||||||
|
pc: peerConnection
|
||||||
|
};
|
||||||
|
state.webrtc.peerConnections[clientId] = entry;
|
||||||
|
|
||||||
|
addLocalTracksToPeerConnection(peerConnection);
|
||||||
|
|
||||||
|
peerConnection.onicecandidate = function(evt) {
|
||||||
|
if (!evt || !evt.candidate) { return; }
|
||||||
|
sendWebRtcSignal(clientId, {
|
||||||
|
type: "candidate",
|
||||||
|
candidate: evt.candidate
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
peerConnection.onconnectionstatechange = function() {
|
||||||
|
pushRecord({
|
||||||
|
kind: "webrtc.connectionState",
|
||||||
|
client_id: clientId,
|
||||||
|
state: peerConnection.connectionState
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
peerConnection.ontrack = function(evt) {
|
||||||
|
entry.remoteStream = evt && evt.streams && evt.streams.length ? evt.streams[0] : null;
|
||||||
|
pushRecord({
|
||||||
|
kind: "webrtc.remoteTrack",
|
||||||
|
client_id: clientId,
|
||||||
|
has_stream: !!entry.remoteStream
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
pushRecord({kind: "webrtc.peerCreated", client_id: clientId});
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeCreateOffer(clientId) {
|
||||||
|
var entry = ensurePeerConnection(clientId);
|
||||||
|
if (!entry || !entry.pc || typeof entry.pc.createOffer !== "function") { return; }
|
||||||
|
|
||||||
|
entry.pc.createOffer()
|
||||||
|
.then(function(offer) {
|
||||||
|
return entry.pc.setLocalDescription(offer).then(function() {
|
||||||
|
sendWebRtcSignal(clientId, {type: "offer", sdp: offer});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
pushRecord({
|
||||||
|
kind: "webrtc.offerError",
|
||||||
|
client_id: clientId,
|
||||||
|
error: (err && err.message) ? err.message : String(err)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWebRtcSignal(fromClientId, signalPayload) {
|
||||||
|
if (!state.webrtc.enabled || !signalPayload || !fromClientId) { return false; }
|
||||||
|
var entry = ensurePeerConnection(fromClientId);
|
||||||
|
if (!entry || !entry.pc) { return true; }
|
||||||
|
|
||||||
|
var pc = entry.pc;
|
||||||
|
if (signalPayload.type === "offer") {
|
||||||
|
pc.setRemoteDescription(signalPayload.sdp)
|
||||||
|
.then(function() {
|
||||||
|
return pc.createAnswer();
|
||||||
|
})
|
||||||
|
.then(function(answer) {
|
||||||
|
return pc.setLocalDescription(answer).then(function() {
|
||||||
|
sendWebRtcSignal(fromClientId, {type: "answer", sdp: answer});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
pushRecord({
|
||||||
|
kind: "webrtc.answerError",
|
||||||
|
client_id: fromClientId,
|
||||||
|
error: (err && err.message) ? err.message : String(err)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signalPayload.type === "answer") {
|
||||||
|
pc.setRemoteDescription(signalPayload.sdp).catch(function(err) {
|
||||||
|
pushRecord({
|
||||||
|
kind: "webrtc.setAnswerError",
|
||||||
|
client_id: fromClientId,
|
||||||
|
error: (err && err.message) ? err.message : String(err)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signalPayload.type === "candidate") {
|
||||||
|
if (signalPayload.candidate && typeof pc.addIceCandidate === "function") {
|
||||||
|
pc.addIceCandidate(signalPayload.candidate).catch(function(err) {
|
||||||
|
pushRecord({
|
||||||
|
kind: "webrtc.addCandidateError",
|
||||||
|
client_id: fromClientId,
|
||||||
|
error: (err && err.message) ? err.message : String(err)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function installInstrumentationWrappers(target) {
|
function installInstrumentationWrappers(target) {
|
||||||
Object.keys(target).forEach(function(key) {
|
Object.keys(target).forEach(function(key) {
|
||||||
var original = target[key];
|
var original = target[key];
|
||||||
|
|
@ -309,6 +669,11 @@
|
||||||
error: state.localMedia.error,
|
error: state.localMedia.error,
|
||||||
hasStream: !!state.localMedia.stream,
|
hasStream: !!state.localMedia.stream,
|
||||||
autoStartEnabled: !!state.localMedia.autoStartEnabled
|
autoStartEnabled: !!state.localMedia.autoStartEnabled
|
||||||
|
},
|
||||||
|
webrtc: {
|
||||||
|
enabled: !!state.webrtc.enabled,
|
||||||
|
localClientId: getSelfClientId(),
|
||||||
|
peerCount: Object.keys(state.webrtc.peerConnections).length
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
recorder: {
|
recorder: {
|
||||||
|
|
@ -384,6 +749,9 @@
|
||||||
}
|
}
|
||||||
pushRecord({kind: "webClientSessionState", event: "JoinSession", session: safeSerialize(sessionDescriptor)});
|
pushRecord({kind: "webClientSessionState", event: "JoinSession", session: safeSerialize(sessionDescriptor)});
|
||||||
startLocalMediaIfEnabled();
|
startLocalMediaIfEnabled();
|
||||||
|
if (state.webrtc.enabled) {
|
||||||
|
ensureLocalMediaForWebRtc();
|
||||||
|
}
|
||||||
return invokeDelegate("JoinSession", arguments);
|
return invokeDelegate("JoinSession", arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -391,17 +759,132 @@
|
||||||
state.inSession = false;
|
state.inSession = false;
|
||||||
state.currentSessionId = null;
|
state.currentSessionId = null;
|
||||||
pushRecord({kind: "webClientSessionState", event: "LeaveSession", session: safeSerialize(sessionDescriptor)});
|
pushRecord({kind: "webClientSessionState", event: "LeaveSession", session: safeSerialize(sessionDescriptor)});
|
||||||
|
Object.keys(state.webrtc.peerConnections).forEach(function(clientId) {
|
||||||
|
closePeerConnection(clientId);
|
||||||
|
});
|
||||||
if (!state.inSessionPage) {
|
if (!state.inSessionPage) {
|
||||||
stopLocalMedia();
|
stopLocalMedia();
|
||||||
}
|
}
|
||||||
return invokeDelegate("LeaveSession", arguments);
|
return invokeDelegate("LeaveSession", arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.SessionGetAllControlState = function(isMasterOrPersonal) {
|
||||||
|
ensureControlStateCache(isMasterOrPersonal);
|
||||||
|
var key = modeKey(isMasterOrPersonal);
|
||||||
|
var values = Object.keys(state.controlState[key]).map(function(mixerId) {
|
||||||
|
return deepClone(state.controlState[key][mixerId]);
|
||||||
|
});
|
||||||
|
pushRecord({
|
||||||
|
kind: "webClientControlStateRead",
|
||||||
|
mode: key,
|
||||||
|
count: values.length
|
||||||
|
});
|
||||||
|
return values;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.SessionSetControlState = function(mixerId, isMasterOrPersonal) {
|
||||||
|
var updated = applyTrackVolumeToCache(mixerId, isMasterOrPersonal);
|
||||||
|
updateLocalTrackMuteFromControlState(updated);
|
||||||
|
|
||||||
|
if (context.trackVolumeObject && context.trackVolumeObject.broadcast) {
|
||||||
|
scheduleTrackSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
pushRecord({
|
||||||
|
kind: "webClientControlStateSet",
|
||||||
|
mixer_id: mixerId,
|
||||||
|
mode: modeKey(isMasterOrPersonal),
|
||||||
|
state: deepClone(updated),
|
||||||
|
broadcast: !!(context.trackVolumeObject && context.trackVolumeObject.broadcast)
|
||||||
|
});
|
||||||
|
|
||||||
|
return invokeDelegate("SessionSetControlState", arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.UpdateSessionInfo = function(sessionInfo) {
|
||||||
|
if (sessionInfo && sessionInfo.id) {
|
||||||
|
state.currentSessionId = sessionInfo.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sessionInfo && sessionInfo.participants && sessionInfo.participants.length) {
|
||||||
|
var nextParticipants = {};
|
||||||
|
sessionInfo.participants.forEach(function(participant) {
|
||||||
|
if (!participant || !participant.client_id) { return; }
|
||||||
|
nextParticipants[participant.client_id] = deepClone(participant);
|
||||||
|
});
|
||||||
|
state.participants = nextParticipants;
|
||||||
|
}
|
||||||
|
|
||||||
|
return invokeDelegate("UpdateSessionInfo", arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ParticipantJoined = function(session, participant) {
|
||||||
|
if (participant && participant.client_id) {
|
||||||
|
state.participants[participant.client_id] = deepClone(participant);
|
||||||
|
if (state.webrtc.enabled) {
|
||||||
|
var selfClientId = getSelfClientId();
|
||||||
|
if (participant.client_id !== selfClientId) {
|
||||||
|
maybeCreateOffer(participant.client_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return invokeDelegate("ParticipantJoined", arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ParticipantLeft = function(session, participant) {
|
||||||
|
if (participant && participant.client_id) {
|
||||||
|
delete state.participants[participant.client_id];
|
||||||
|
closePeerConnection(participant.client_id);
|
||||||
|
}
|
||||||
|
return invokeDelegate("ParticipantLeft", arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ClientJoinedSession = function(sourceUserId, clientId, sessionId) {
|
||||||
|
if (clientId) {
|
||||||
|
state.participants[clientId] = state.participants[clientId] || {client_id: clientId};
|
||||||
|
if (state.webrtc.enabled) {
|
||||||
|
var selfClientId = getSelfClientId();
|
||||||
|
if (clientId !== selfClientId) {
|
||||||
|
maybeCreateOffer(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return invokeDelegate("ClientJoinedSession", arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ClientLeftSession = function(sourceUserId, clientId, sessionId) {
|
||||||
|
if (clientId) {
|
||||||
|
delete state.participants[clientId];
|
||||||
|
closePeerConnection(clientId);
|
||||||
|
}
|
||||||
|
return invokeDelegate("ClientLeftSession", arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.P2PMessageReceived = function(from, payload) {
|
||||||
|
var parsedPayload = payload;
|
||||||
|
if (typeof parsedPayload === "string") {
|
||||||
|
try {
|
||||||
|
parsedPayload = JSON.parse(parsedPayload);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedPayload && parsedPayload.jk_webclient_webrtc && parsedPayload.payload) {
|
||||||
|
onWebRtcSignal(from, parsedPayload.payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return invokeDelegate("P2PMessageReceived", [from, payload]);
|
||||||
|
};
|
||||||
|
|
||||||
// Keep a handle to the delegate for incremental replacement/testing.
|
// Keep a handle to the delegate for incremental replacement/testing.
|
||||||
this.__delegate = inner;
|
this.__delegate = inner;
|
||||||
this.__state = state;
|
this.__state = state;
|
||||||
this.__recorder = recorder;
|
this.__recorder = recorder;
|
||||||
|
|
||||||
|
state.webrtc.enabled = readWebRtcEnabledFlag();
|
||||||
|
if (state.webrtc.enabled) {
|
||||||
|
pushRecord({kind: "webrtc.enabled"});
|
||||||
|
}
|
||||||
|
|
||||||
recorder.enabled = recorderEnabledByDefault();
|
recorder.enabled = recorderEnabledByDefault();
|
||||||
if (recorder.enabled) {
|
if (recorder.enabled) {
|
||||||
pushRecord({kind: "webClientRecorder", action: "auto-enable"});
|
pushRecord({kind: "webClientRecorder", action: "auto-enable"});
|
||||||
|
|
|
||||||
|
|
@ -93,5 +93,7 @@ module ClientHelper
|
||||||
gon.stripe_publishable_key = Rails.application.config.stripe[:publishable_key]
|
gon.stripe_publishable_key = Rails.application.config.stripe[:publishable_key]
|
||||||
gon.spa_origin_url = Rails.application.config.spa_origin_url
|
gon.spa_origin_url = Rails.application.config.spa_origin_url
|
||||||
gon.log_to_server = (ENV['LOG_TO_SERVER'].to_s == '1')
|
gon.log_to_server = (ENV['LOG_TO_SERVER'].to_s == '1')
|
||||||
|
gon.legacy_qt_webkit_client = legacy_qt_webkit_client?
|
||||||
|
gon.show_mode_flags_overlay = Rails.env.development?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,22 @@
|
||||||
{
|
{
|
||||||
"name": "jam-web",
|
"name": "jam-web",
|
||||||
|
"scripts": {
|
||||||
|
"test:playwright": "npx playwright test -c spec/playwright/playwright.config.js",
|
||||||
|
"test:playwright:headed": "HEADLESS=false npx playwright test -c spec/playwright/playwright.config.js"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"browserify": "~> 6.3",
|
"browserify": "~> 6.3",
|
||||||
"browserify-incremental": "^1.4.0",
|
"browserify-incremental": "^1.4.0",
|
||||||
"coffeeify": "~> 0.6",
|
|
||||||
"reactify": "^0.17.1",
|
|
||||||
"coffee-reactify": "~>3.0.0",
|
"coffee-reactify": "~>3.0.0",
|
||||||
|
"coffeeify": "~> 0.6",
|
||||||
"react": "^0.12.0",
|
"react": "^0.12.0",
|
||||||
"react-tools": "^0.12.1"
|
"react-tools": "^0.12.1",
|
||||||
|
"reactify": "^0.17.1"
|
||||||
},
|
},
|
||||||
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.58.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue