jam-cloud/web/app/assets/javascripts/wizard/gear_utils.js

785 lines
26 KiB
JavaScript

/**
* Common utility functions.
*/
(function (context, $) {
"use strict";
context.JK = context.JK || {};
var gearUtils = {};
var rest = new context.JK.Rest();
context.JK.GearUtils = gearUtils;
var logger = context.JK.logger;
var ALERT_NAMES = context.JK.ALERT_NAMES;
var ASSIGNMENT = context.JK.ASSIGNMENT;
var VOICE_CHAT = context.JK.VOICE_CHAT;
var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR;
var EVENTS = context.JK.EVENTS;
var SYSTEM_DEFAULT_PLAYBACK_ONLY = 'System Default (Playback Only)';
var JAMKAZAM_VIRTUAL_INPUT = "JamKazam Virtual Input";
context.JK.GearUtilsInstance = gearUtils;
gearUtils.SKIPPED_NETWORK_TEST = -1; // we store a negative 1 to mean that we let the user skip.
gearUtils.SYSTEM_DEFAULT_PLAYBACK_ONLY = SYSTEM_DEFAULT_PLAYBACK_ONLY;
gearUtils.JAMKAZAM_VIRTUAL_INPUT = JAMKAZAM_VIRTUAL_INPUT;
gearUtils.skippedNetworkTest = false; // we allow someone to play in session (for one client run) if it's our fault they can't network test score
var reloadAudioTimeout = null;
var currentAudioRestartLocation = null;
// checks if it's an assigned OUTPUT or ASSIGNED CHAT
gearUtils.isChannelAssigned = function (channel) {
return channel.assignment == ASSIGNMENT.CHAT || channel.assignment == ASSIGNMENT.OUTPUT || channel.assignment > 0;
}
gearUtils.resyncAudio = async function() {
async function backendMixerChange (e, data) {
if (data.text == 'deferred') {
context.JK.offBackendEvent(ALERT_NAMES.BACKEND_MIXER_CHANGE, 'gearUtilsResyncAudio', backendMixerChange)
console.log("context.jamClient.FTUEGetExpectedLatency()", await context.jamClient.FTUEGetExpectedLatency().latency)
deferred.resolve();
}
;
}
var deferred = new $.Deferred();
context.JK.onBackendEvent(ALERT_NAMES.BACKEND_MIXER_CHANGE, 'gearUtilsResyncAudio', backendMixerChange);
await context.jamClient.SessionAudioResync();
// give backend 5 seconds to timeout
deferred.timer = setTimeout(function() {
deferred.rejectWith('timeout');
context.JK.offBackendEvent(ALERT_NAMES.BACKEND_MIXER_CHANGE, 'gearUtilsResyncAudio', backendMixerChange)
}, 5000);
return deferred;
}
// to play with others, you have to have inputs,
// as well have a score below 20 ms
gearUtils.canPlayWithOthers = function(profile) {
var isNoInputProfile = gearUtils.isNoInputProfile(profile);
var expectedLatency = context.jamClient.FTUEGetExpectedLatency();
var audioLatency = expectedLatency ? expectedLatency.latency : null;
// only compute high latency if the value is known. Also don't accept insanely largue values
var highLatency = false;
if(audioLatency) {
highLatency = audioLatency > 20 && audioLatency < 1000;
}
var networkScore = context.jamClient.GetNetworkTestScore();
var badNetworkScore = networkScore < 2;
// for now, ignore latency
if(gon.global.gear_check_ignore_high_latency) {
highLatency = false
}
return {
canPlay: !isNoInputProfile && !highLatency,
isNoInputProfile: isNoInputProfile,
badNetworkScore: badNetworkScore,
highLatency: highLatency,
audioLatency: audioLatency,
networkScore: networkScore,
}
}
gearUtils.isNoInputProfile = async function(profile) {
if (profile === undefined) {
profile = await context.jamClient.LastUsedProfileName();
}
return profile == SYSTEM_DEFAULT_PLAYBACK_ONLY;
}
gearUtils.canBeConfigured = function(profile) {
return profile != SYSTEM_DEFAULT_PLAYBACK_ONLY;
}
gearUtils.createProfileName = function (deviceInfo, chatName) {
var isSameInOut = deviceInfo.input.id == deviceInfo.output.id;
var name = null;
if (isSameInOut) {
name = "In/Out: " + deviceInfo.input.info.displayName;
}
else {
name = "In: " + deviceInfo.input.info.displayName + ", Out: " + deviceInfo.output.info.displayName
}
logger.debug("creating profile name: " + name);
return name;
}
gearUtils.selectedDeviceInfo = async function (audioInputDeviceId, audioOutputDeviceId, deviceInformation) {
if (!audioInputDeviceId) {
logger.debug("gearUtils.selectedDeviceInfo: no active input device");
return null;
}
if (!audioOutputDeviceId) {
logger.debug("gearUtils.selectedDeviceInfo: no active output device");
return null;
}
if (!deviceInformation) {
deviceInformation = await gearUtils.loadDeviceInfo();
}
var input = deviceInformation[audioInputDeviceId];
var output = deviceInformation[audioOutputDeviceId];
var inputBehavior = AUDIO_DEVICE_BEHAVIOR[input.type];
var outputBehavior = AUDIO_DEVICE_BEHAVIOR[output.type];
return {
input: {
id: audioInputDeviceId,
info: input,
behavior: inputBehavior
},
output: {
id: audioOutputDeviceId,
info: output,
behavior: outputBehavior
}
}
}
gearUtils.loadDeviceInfo = async function () {
var operatingSystem = await context.JK.GetOSAsString();
// should return one of:
// * MacOSX_builtin
// * MACOSX_interface
// * Win32_wdm
// * Win32_asio
// * Win32_asio4all
// * Linux
async function determineDeviceType(deviceId, displayName) {
if (operatingSystem == "MacOSX") {
if (displayName.toLowerCase().trim().indexOf("built-in") == 0) {
return "MacOSX_builtin";
}
else {
return "MacOSX_interface";
}
}
else if (operatingSystem == "Win32") {
if (await context.jamClient.FTUEIsMusicDeviceWDM(deviceId)) {
return "Win32_wdm";
}
else if (displayName.toLowerCase().indexOf("asio4all") > -1) {
return "Win32_asio4all"
}
else {
return "Win32_asio";
}
}
else {
return "Linux";
}
}
var devices = await context.jamClient.FTUEGetAudioDevices();
//logger.debug("FTUEGetAudioDevices: " + JSON.stringify(devices));
var loadedDevices = {};
// augment these devices by determining their type
context._.each(devices.devices, async function (device) {
if (device.name == "JamKazam Virtual Monitor") {
return;
}
if (device.name == JAMKAZAM_VIRTUAL_INPUT) {
return;
}
if ((device.name == 'Default Input' || device.name == 'Default Output') && operatingSystem == 'Win32'){
console.log("dropping " + device.name)
return;
}
var deviceInfo = {};
deviceInfo.id = device.guid;
deviceInfo.port_audio_name = device.port_audio_name;
deviceInfo.type = await determineDeviceType(device.guid, device.display_name);
console.log('device', device)
console.log('deviceInfo', deviceInfo)
deviceInfo.displayType = AUDIO_DEVICE_BEHAVIOR[deviceInfo.type].display;
deviceInfo.displayName = device.display_name;
deviceInfo.inputCount = device.input_count;
deviceInfo.outputCount = device.output_count;
loadedDevices[device.guid] = deviceInfo;
})
logger.debug(context.JK.dlen(loadedDevices) + " devices loaded.", loadedDevices);
return loadedDevices;
}
gearUtils.updateDefaultBuffers = async function (selectedDeviceInfo, frameBuffers) {
function hasWDMAssociated() {
return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_wdm' || selectedDeviceInfo.output.info.type == 'Win32_wdm')
}
function hasASIOAssociated() {
return selectedDeviceInfo && (selectedDeviceInfo.input.info.type == 'Win32_asio' || selectedDeviceInfo.output.info.type == 'Win32_asio')
}
// handle specific framesize settings
if (hasWDMAssociated() || hasASIOAssociated()) {
var framesize = frameBuffers.selectedFramesize();
if (framesize == 2.5) {
// if there is a WDM device, start off at 1/1 due to empirically observed issues with 0/0
if (hasWDMAssociated()) {
logger.debug("setting default buffers to 1/1");
frameBuffers.setBufferIn('1');
frameBuffers.setBufferOut('1');
}
else {
// otherwise, it's ASIO, so go with 0/0
logger.debug("setting default buffers to 0/0");
frameBuffers.setBufferIn('0');
frameBuffers.setBufferOut('0');
}
}
else if (framesize == 5) {
logger.debug("setting default buffers to 3/2");
frameBuffers.setBufferIn('3');
frameBuffers.setBufferOut('2');
}
else {
logger.debug("setting default buffers to 2/2");
frameBuffers.setBufferIn('2');
frameBuffers.setBufferOut('2');
}
}
else {
logger.debug("setting default buffers to 0/0");
frameBuffers.setBufferIn(0);
frameBuffers.setBufferOut(0);
}
await context.jamClient.FTUESetInputLatency(frameBuffers.selectedBufferIn());
await context.jamClient.FTUESetOutputLatency(frameBuffers.selectedBufferOut());
}
gearUtils.ftueSummary = async function (operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
return {
os: operatingSystem,
version: await context.jamClient.ClientUpdateVersion(),
success: gearTest.isGoodFtue(),
automated: isAutomated,
score: {
validLatencyScore: gearTest.isValidLatencyScore(),
validIOScore: gearTest.isValidIOScore(),
latencyScore: gearTest.getLatencyScore(),
ioScore: gearTest.getIOScore(),
},
audioParameters: {
frameSize: frameBuffers.selectedFramesize(),
bufferIn: frameBuffers.selectedBufferIn(),
bufferOut: frameBuffers.selectedBufferOut(),
},
devices: deviceInformation,
selectedDevice: selectedDeviceInfo
}
}
/**
* Lists all profiles, but marks profiles good: true/false.
* Also, current:true/false indicates which profile is active. (at most 1 profile will be marked current)
* This is to provide a unified view of FTUEGetAllAudioConfigurations & FTUEGetGoodAudioConfigurations
* @returns an array of profiles, where each profile is: {id: profile-name, good: boolean, class: 'bad' | 'good', current: boolean }
*/
gearUtils.getProfiles = async function () {
var all = await context.jamClient.FTUEGetAllAudioConfigurations();
var good = await context.jamClient.FTUEGetGoodAudioConfigurations();
var current = await context.jamClient.LastUsedProfileName();
var profiles = [];
context._.each(all, function (item) {
profiles.push({id: item, good: false, class: 'bad', current: current == item})
});
if (good) {
for (var i = 0; i < good.length; i++) {
for (var j = 0; j < profiles.length; j++) {
if (good[i] == profiles[j].id) {
profiles[j].good = true;
profiles[j].class = 'good';
break;
}
}
}
}
return profiles;
}
gearUtils.postDiagnostic = function (operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated) {
rest.createDiagnostic({
type: 'GEAR_SELECTION',
data: {
client_type: context.JK.clientType(),
client_id: context.JK.JamServer.clientID,
summary: gearUtils.ftueSummary(operatingSystem, deviceInformation, selectedDeviceInfo, gearTest, frameBuffers, isAutomated)
}
});
}
// complete list of possibly chatInputs, whether currently assigned as the chat channel or not
// each item should be {id: channelId, name: channelName, assignment: channel assignment}
gearUtils.getChatInputs = async function () {
var musicPorts = await jamClient.FTUEGetChannels();
//var chatsOnCurrentDevice = context.jamClient.FTUEGetChatInputs(true);
var chatsOnOtherDevices = await context.jamClient.FTUEGetChatInputs(false, true);
// remove all virtual/remote junk form chat inputs. Their key contains' JamKazam when it's Virtual Input or Remote
Object.keys(chatsOnOtherDevices).forEach(function(key) {
if(key.indexOf('JamKazam') > -1) {
delete chatsOnOtherDevices[key]
}
} )
// this wasapi logic is this: if there are any non-WASAPI items, present only them, because WASAPI is very high latency and
// highly desirable. But if the user ONLY has wasapi, then fine, we show them (by not deleting them from the list)
var allWasapi = true;
context._.each(chatsOnOtherDevices, function (chatChannelName, chatChannelId) {
if(chatChannelName.indexOf('WASAPI') == -1) {
allWasapi = false;
return false
}
})
if(!allWasapi) {
Object.keys(chatsOnOtherDevices).forEach(function(key) {
if(chatsOnOtherDevices[key].indexOf('WASAPI') > -1) {
// delete chatsOnOtherDevices[key]
}
} )
}
var chatInputs = [];
//context._.each(musicPorts.inputs, function(input) {
// chatInputs.push({id: input.id, name: input.name, assignment:input.assignment});
//});
var deDupper = {};
context._.each(musicPorts.inputs, function (input) {
var chatInput = {id: input.id, name: input.name, assignment: input.assignment};
if (!deDupper[input.id]) {
if (input.assignment <= 0) {
chatInputs.push(chatInput);
deDupper[input.id] = chatInput;
}
}
});
/**context._.each(chatsOnCurrentDevice, function(chatChannelName, chatChannelId) {
var chatInput = {id: chatChannelId, name: chatChannelName, assignment: ASSIGNMENT.UNASSIGNED};
if(!deDupper[chatInput.id]) {
var assignment = context.jamClient.TrackGetAssignment(chatChannelId, true);
if(assignment <= 0) {
chatInputs.push(chatInput);
deDupper[chatInput.id] = chatInput;
}
}
})*/
context._.each(chatsOnOtherDevices, async function (chatChannelName, chatChannelId) {
var chatInput = {id: chatChannelId, name: chatChannelName, assignment: null};
if (!deDupper[chatInput.id]) {
var assignment = await context.jamClient.TrackGetAssignment(chatChannelId, true);
chatInput.assignment = assignment;
chatInputs.push(chatInput);
deDupper[chatInput.id] = chatInput;
}
})
return chatInputs;
}
gearUtils.isChannelAvailableForChat = function (chatChannelId, musicPorts) {
var result = true;
context._.each(musicPorts.inputs, function (inputChannel) {
// if the channel is currently assigned to a track, it not unassigned
if (inputChannel.id == chatChannelId && (inputChannel.assignment > 0)) {
result = false;
return false; // break
}
});
return result;
}
// if the user has a good user network score, immediately returns with a resolved deferred object.
// if not, the user will have the network test dialog prompted... once it's closed, then you'll be told reject() if score is still bad, or resolve() if now good
// gearUtils.guardAgainstBadNetworkScore = function (app) {
// var deferred = new $.Deferred();
// if (!gearUtils.validNetworkScore()) {
// // invalid network test score. They have to score to move on
// app.layout.showDialog('network-test').one(EVENTS.DIALOG_CLOSED, function () {
// if (gearUtils.validNetworkScore()) {
// deferred.resolve();
// }
// else {
// deferred.reject();
// }
// });
// }
// else {
// deferred.resolve();
// }
// return deferred;
// }
gearUtils.guardAgainstBadNetworkScore = function (app) {
return new Promise(async function(resolve, reject){
if (! await gearUtils.validNetworkScore()) {
// invalid network test score. They have to score to move on
app.layout.showDialog('network-test').one(EVENTS.DIALOG_CLOSED, async function () {
if (await gearUtils.validNetworkScore()) {
resolve();
}
else {
reject();
}
});
}
else {
resolve();
}
})
}
// XXX this isn't quite right... it needs to check if a good device is *active*
// but seen too many problems so far with the backend not reporting any profile active
gearUtils.hasGoodActiveProfile = async function (verifyTracks) {
var hasOneConfigureDevice = await context.JK.hasOneConfiguredDevice();
logger.debug("hasGoodActiveProfile: " + hasOneConfigureDevice ? "devices='has at least one configured device' " : "devices='has no configured device' ")
return hasOneConfigureDevice;
}
gearUtils.jamClientFTUEGetAllAudioConfigurations = async function (app) {
const resp = await context.jamClient.FTUEGetAllAudioConfigurations();
return resp;
}
// if the user does not have any profiles, show the FTUE
gearUtils.guardAgainstInvalidGearConfiguration = function (app) {
return new Promise(async function(resolve, reject){
if (await gearUtils.jamClientFTUEGetAllAudioConfigurations().length === 0) {
app.layout.showDialog('gear-wizard').one(EVENTS.DIALOG_CLOSED, async function () {
if (await gearUtils.hasGoodActiveProfile() && gearUtils.validNetworkScore()) {
resolve();
}
else {
reject();
}
});
}
else {
resolve();
}
})
}
context.JK.guardAgainstSinglePlayerProfile = function(app, beforeCallback) {
var deferred = new $.Deferred();
var canPlayWithOthers = context.JK.GearUtilsInstance.canPlayWithOthers();
if(!canPlayWithOthers.canPlay) {
logger.debug("guarding against single player profile")
var $dialog = app.layout.showDialog('single-player-profile-dialog');
// so that callers can check dialog result
canPlayWithOthers.dialog = $dialog;
// allow callers to take action before default behavior
if(beforeCallback) {
$dialog.one(EVENTS.DIALOG_CLOSED, beforeCallback);
}
$dialog.one(EVENTS.DIALOG_CLOSED, function(e, data) {
if(data.canceled) {
deferred.rejectWith(canPlayWithOthers, [{ reason: 'canceled', controlled_location:false }])
}
else {
if(data.result.choice == 'private_session') {
var data = {
createType: 'quick-start',
timezone: {},
recurring_mode: {},
language: {},
band: {},
musician_access: {},
fans_access: {},
rsvp_slots: [],
open_rsvps: false
};
context.JK.privateSessionSettings(data)
context.JK.createSession(app, data)
.done(function(response) {
var sessionId = response.id;
context.JK.GA.trackSessionCount(true, true, 0);
deferred.rejectWith(canPlayWithOthers, [{ reason: 'private_session', controlled_location:true }])
// we redirect to the session screen, which handles the REST call to POST /participants.
logger.debug("joining session screen: " + sessionId)
context.location = '/client#/session/' + sessionId;
})
.fail(function(jqXHR) {
deferred.rejectWith(canPlayWithOthers, [{ reason: 'gear_setup', controlled_location:false }])
logger.debug("unable to schedule a private session")
app.notifyServerError(jqXHR, "Unable to schedule a private session");
})
}
else if(data.result.choice == 'gear_setup') {
deferred.rejectWith(canPlayWithOthers, [{ reason: data.result.choice,controlled_location:true }])
window.location = '/client#/account/audio'
}
else
{
deferred.rejectWith(canPlayWithOthers, [{ reason: 'unknown', controlled_location:false }])
logger.error("unknown choice: " + data.result.choice)
alert("unknown choice: " + data.result.choice)
}
}
})
}
else {
deferred.resolveWith(canPlayWithOthers)
}
return deferred;
}
// gearUtils.guardAgainstActiveProfileMissing = function (app, backendInfo) {
// var deferred = new $.Deferred();
// logger.debug("guardAgainstActiveProfileMissing: backendInfo %o", backendInfo);
// if (backendInfo.error && backendInfo['reason'] == 'no_profile' && context.jamClient.FTUEGetAllAudioConfigurations().length > 0) {
// // if the backend says we have no_profile, but we have profiles , send them to the audio profile screen
// // this should be a very rare path
// deferred.reject({reason: 'handled', nav: '/client#/account/audio'});
// context.JK.Banner.showAlert('No Active Profile', 'We\'ve sent you to the audio profile screen to remedy the fact that you have no active audio profile. Please select ACTIVATE on an existing profile, or select ADD NEW GEAR to add a new profile.');
// }
// else if (backendInfo.error && backendInfo['reason'] == 'device_failure') {
// app.layout.showDialog('audio-profile-invalid-dialog')
// .one(EVENTS.DIALOG_CLOSED, function (e, data) {
// if (!data.result || data.result == 'cancel') {
// deferred.reject({reason: 'handled', nav: 'BACK'});
// }
// else if (data.result == 'configure_gear') {
// deferred.reject({reason: 'handled', nav: '/client#/account/audio'});
// }
// else if (data.result == 'session') {
// deferred.resolve();
// }
// else {
// logger.error("unknown result condition in audio-profile-invalid-dialog: " + data.result)
// deferred.reject()
// }
// });
// }
// else {
// deferred.resolve();
// }
// return deferred;
// }
gearUtils.guardAgainstActiveProfileMissing = function (app, backendInfo) {
return new Promise(async function(resolve, reject) {
logger.debug("guardAgainstActiveProfileMissing: backendInfo %o", backendInfo);
if (backendInfo.error && backendInfo['reason'] == 'no_profile' && await context.jamClient.FTUEGetAllAudioConfigurations().length > 0) {
// if the backend says we have no_profile, but we have profiles , send them to the audio profile screen
// this should be a very rare path
reject({reason: 'handled', nav: '/client#/account/audio'});
context.JK.Banner.showAlert('No Active Profile', 'We\'ve sent you to the audio profile screen to remedy the fact that you have no active audio profile. Please select ACTIVATE on an existing profile, or select ADD NEW GEAR to add a new profile.');
}
else if (backendInfo.error && backendInfo['reason'] == 'device_failure') {
app.layout.showDialog('audio-profile-invalid-dialog')
.one(EVENTS.DIALOG_CLOSED, function (e, data) {
if (!data.result || data.result == 'cancel') {
reject({reason: 'handled', nav: 'BACK'});
}
else if (data.result == 'configure_gear') {
reject({reason: 'handled', nav: '/client#/account/audio'});
}
else if (data.result == 'session') {
resolve();
}
else {
logger.error("unknown result condition in audio-profile-invalid-dialog: " + data.result)
reject()
}
});
}
else {
resolve();
}
})
}
// tests both device config, and network score
gearUtils.guardAgainstInvalidConfiguration = function (app, verifyNetworkScore) {
return new Promise(async function(resolve, reject){
try {
await gearUtils.guardAgainstInvalidGearConfiguration(app)
if(verifyNetworkScore) {
gearUtils.guardAgainstBadNetworkScore(app)
}else{
resolve()
}
} catch (error) {
reject();
}
})
}
gearUtils.skipNetworkTest = async function () {
await context.jamClient.SetNetworkTestScore(gearUtils.SKIPPED_NETWORK_TEST);
gearUtils.skippedNetworkTest = true;
}
gearUtils.isNetworkTestSkipped = function () {
return gearUtils.skippedNetworkTest;
}
gearUtils.validNetworkScore = async function () {
return !gon.global.network_test_required || gearUtils.skippedNetworkTest || await context.jamClient.GetNetworkTestScore() >= 2;
}
gearUtils.isRestartingAudio = function () {
return !!reloadAudioTimeout;
}
gearUtils.scheduleAudioRestart = function (location, initial_delay, beforeScan, afterScan, cancelScan) {
logger.debug("scheduleAudioRestart: (from " + location + ")")
var cancellable = true;
function clearAudioReloadTimer() {
if (!cancellable) {
return;
}
if (cancelScan) {
cancelScan();
}
else if (afterScan) {
afterScan(true);
}
clearTimeout(reloadAudioTimeout);
reloadAudioTimeout = null;
currentAudioRestartLocation = null;
cancellable = false;
}
// refresh timer if outstanding
if (reloadAudioTimeout) {
logger.debug("scheduleAudioRestart: clearing timeout (from " + location + ")")
clearTimeout(reloadAudioTimeout);
}
currentAudioRestartLocation = location;
if (beforeScan) {
beforeScan();
}
reloadAudioTimeout = setTimeout(function () {
logger.debug("scheduleAudioRestart: rescan beginning (from " + location + ")")
reloadAudioTimeout = null;
currentAudioRestartLocation = null;
cancellable = false;
if (afterScan) {
afterScan(false);
}
}, initial_delay ? initial_delay : 5000);
return clearAudioReloadTimer;
}
gearUtils.bootstrapDefaultPlaybackProfile = function () {
var profiles = gearUtils.getProfiles();
var foundSystemDefaultPlaybackOnly = false
context._.each(profiles, function (profile) {
if (profile.id == SYSTEM_DEFAULT_PLAYBACK_ONLY) {
foundSystemDefaultPlaybackOnly = true
return false;
}
})
if (!foundSystemDefaultPlaybackOnly) {
logger.debug("creating system default profile (playback only)")
if(!gearUtils.createDefaultPlaybackOnlyProfile()) {
logger.error("unable to create the default playback profile!");
}
}
}
gearUtils.createDefaultPlaybackOnlyProfile = function () {
var eMixerInputSampleRate = {
JAMKAZAM_AUTO_SR: 0,
USE_DEVICE_DEFAULT_SR: 1,
PREFER_44: 2,
PREFER_48: 3,
PREFER_96: 4
}
// null//upgrade protect
if(context.jamClient.FTUECreateUpdatePlayBackProfile) {
return context.jamClient.FTUECreateUpdatePlayBackProfile(SYSTEM_DEFAULT_PLAYBACK_ONLY,
eMixerInputSampleRate.USE_DEVICE_DEFAULT_SR,
0, // buffering
false); // start audio
}
else {
return false;
}
}
})(window, jQuery);