* VRFS-3494 - show popup when video window launches for the 1st time to offer guidance

This commit is contained in:
Seth Call 2015-08-30 05:00:00 -05:00
parent c3f81a4d23
commit 9420cebad4
32 changed files with 1088 additions and 28 deletions

View File

@ -113,6 +113,21 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
@user.delete_mod(User::MOD_GEAR, User::MOD_GEAR_FRAME_OPTIONS)
end
if params[:jam_ruby_user][:how_to_use_video_no_show].to_i == 1
@user.mod_merge({User::MOD_NO_SHOW => {User::HOWTO_USE_VIDEO_NOSHOW => true}})
else
@user.delete_mod(User::MOD_NO_SHOW, User::HOWTO_USE_VIDEO_NOSHOW)
end
if params[:jam_ruby_user][:configure_video_no_show].to_i == 1
@user.mod_merge({User::MOD_NO_SHOW => {User::CONFIGURE_VIDEO_NOSHOW=> true}})
else
@user.delete_mod(User::MOD_NO_SHOW, User::CONFIGURE_VIDEO_NOSHOW)
end
@user.save!
redirect_to edit_admin_user_path(@user)

View File

@ -9,4 +9,7 @@
= f.input :musician
= f.inputs "Gear Mods" do
= f.input :show_frame_options, as: :boolean
= f.inputs "Do Not Shows" do
= f.input :how_to_use_video_no_show, as: :boolean
= f.input :configure_video_no_show, as: :boolean
= f.actions

View File

@ -27,4 +27,13 @@
def show_frame_options
self.get_gear_mod(MOD_GEAR_FRAME_OPTIONS)
end
def how_to_use_video_no_show
self.get_no_show_mod(HOWTO_USE_VIDEO_NOSHOW)
end
def configure_video_no_show
self.get_no_show_mod(CONFIGURE_VIDEO_NOSHOW)
end
end

View File

@ -1774,7 +1774,7 @@ module JamRuby
end
if count > 500
break
#break
end
end

View File

@ -24,6 +24,8 @@ module JamRuby
MOD_GEAR_FRAME_OPTIONS = "show_frame_options"
MOD_NO_SHOW = "no_show"
HOWTO_USE_VIDEO_NOSHOW = 'how-to-use-video'
CONFIGURE_VIDEO_NOSHOW = 'configure-video'
# MIN/MAX AUDIO LATENCY
MINIMUM_AUDIO_LATENCY = 2

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

View File

@ -4,7 +4,7 @@
context.JK = context.JK || {};
context.JK.AccountVideoProfile = function (app) {
var $webcamViewer = new context.JK.WebcamViewer()
var webcamViewerReact = null;
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
@ -12,16 +12,19 @@
};
app.bindScreen('account/video', screenBindings);
var $root = $("#account-video-profile .webcam-container")
$webcamViewer.init($root, true)
var reactElement = React.createElement(window.WebcamViewer, {isVisible: false});
var reactDomNode = $("#account-video-profile .webcam-container").get(0)
webcamViewerReact = React.render(reactElement, reactDomNode)
}
function beforeShow() {
$webcamViewer.beforeShow()
console.log("webcamViewerReact", webcamViewerReact)
webcamViewerReact.beforeShow()
}
function beforeHide() {
$webcamViewer.beforeHide()
webcamViewerReact.beforeHide()
}
this.beforeShow = beforeShow

View File

@ -131,6 +131,12 @@
// context.JK.CurrentSessionModel.onPlaybackStateChange(type, text);
context.MediaPlaybackActions.playbackStateChange(text);
}
else if(type === ALERT_NAMES.VIDEO_WINDOW_OPENED) {
context.VideoActions.videoWindowOpened()
}
else if(type === ALERT_NAMES.VIDEO_WINDOW_CLOSED) {
context.VideoActions.videoWindowClosed()
}
else if((!context.JK.CurrentSessionModel || !context.JK.CurrentSessionModel.inSession()) &&
(ALERT_NAMES.INPUT_IO_RATE == type || ALERT_NAMES.INPUT_IO_JTR == type || ALERT_NAMES.OUTPUT_IO_RATE == type || ALERT_NAMES.OUTPUT_IO_JTR== type)) {
// squelch these events if not in session

View File

@ -39,4 +39,6 @@ context.JK.ClientInit = class ClientInit
nativeClientInit: () =>
@gearUtils.bootstrapDefaultPlaybackProfile();
window.VideoActions.checkPromptConfigureVideo()

View File

@ -80,20 +80,20 @@
}
function FTUEGetVideoCaptureDeviceNames() {
return ["Built-in Webcam HD"]
return {"xy323ss": "Built-in Webcam HD"}
}
function FTUECurrentSelectedVideoDevice() {
return {"xy323ss": "Built-in Webcam HD"}
//return {"xy323ss": "Built-in Webcam HD"}
return {}
}
function FTUEGetAvailableEncodeVideoResolutions() {
return {
1 : "QCIF (176X144)",
2 : "CIF (352X288)",
3 : "VGA (640X480)",
4 : "4CIF (704X576)",
5 : "1/2WHD (640X360)",
6: "WHD (1280X720)",
7 : "FHD (1920x1080)"
1 : "CIF (352X288)",
2 : "VGA (640X480)",
3 : "4CIF (704X576)",
4 : "1/2WHD (640X360)",
5 : "WHD (1280X720)",
6 : "FHD (1920x1080)"
}
}

View File

@ -116,7 +116,16 @@
SHOW_PREFERENCES : 39, // tell frontend to show preferences dialog
USB_CONNECTED : 40, // tell frontend that a USB device was connected
USB_DISCONNECTED : 41, // tell frontend that a USB device was disconnected
LAST_ALERT : 42
JAM_TRACK_SERVER_ERROR : 42, //error talking with server
BAD_INTERVAL_RATE : 43, //the audio gear is calling back at rate that does not match the expected interval
FIRST_AUDIO_PACKET : 44,// we are receiving audio from peer
NETWORK_PORT_MANGLED : 45, // packet from peer indicates network port is being mangled
NO_GLOBAL_CLOCK_SERVER : 46, //can't reach global clock NTP server
GLOBAL_CLOCK_SYNCED : 47, //clock synced
RECORDING_DONE :48, //the recording writer thread is done
VIDEO_WINDOW_OPENED :49, //video window opened
VIDEO_WINDOW_CLOSED :50,
LAST_ALERT : 51
}
// recreate eThresholdType enum from MixerDialog.h
context.JK.ALERT_TYPES = {
@ -171,7 +180,16 @@
39: {"title": "", "message": ""}, // SHOW_PREFERENCES, //show preferences dialog
40: {"title": "", "message": ""}, // USB_CONNECTED
41: {"title": "", "message": ""}, // USB_DISCONNECTED, // tell frontend that a USB device was disconnected
42: {"title": "", "message": ""} // LAST_ALERT
42: {"title": "", "message": ""}, // JAM_TRACK_SERVER_ERROR
43: {"title": "", "message": ""}, // BAD_INTERVAL_RATE
44: {"title": "", "message": ""}, // FIRST_AUDIO_PACKET
45: {"title": "", "message": ""}, // NETWORK_PORT_MANGLED
46: {"title": "", "message": ""}, // NO_GLOBAL_CLOCK_SERVER
47: {"title": "", "message": ""}, // GLOBAL_CLOCK_SYNCED
48: {"title": "", "message": ""}, // RECORDING_DONE
49: {"title": "", "message": ""}, // VIDEO_WINDOW_OPENED
50: {"title": "", "message": ""}, // VIDEO_WINDOW_CLOSED
51: {"title": "", "message": ""} // LAST_ALERT
};
// add the alert's name to the ALERT_TYPES structure
@ -311,7 +329,9 @@
/** NAMED_MESSAGES means messages that we show to the user (dialogs/banners/whatever), that we have formally named */
context.JK.NAMED_MESSAGES = {
MASTER_VS_PERSONAL_MIX : 'master_vs_personal_mix'
MASTER_VS_PERSONAL_MIX : 'master_vs_personal_mix',
HOWTO_USE_VIDEO_NOSHOW : 'how-to-use-video',
CONFIGURE_VIDEO_NOSHOW : 'configure-video'
}
context.JK.ChannelGroupIds = {

View File

@ -12,6 +12,7 @@
//= require ./react-components/stores/SessionMyTracksStore
//= require ./react-components/stores/SessionOtherTracksStore
//= require ./react-components/stores/SessionMediaTracksStore
//= require ./react-components/stores/VideoStore
//= require_directory ./react-components/stores
//= require_directory ./react-components/mixins
//= require_directory ./react-components

View File

@ -0,0 +1,108 @@
context = window
logger = context.JK.logger
mixins = []
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
accessOpener = false
if window.opener?
try
m = window.opener.MixerStore
accessOpener = true
catch e
if accessOpener
VideoActions = window.opener.VideoActions
VideoStore = window.opener.VideoStore
#mixins.push(Reflux.listenTo(VideoStore, 'onVideoStateChanged'))
@PopupConfigureVideoGear = React.createClass({
mixins: mixins
logger: context.JK.logger
render: () ->
`<div className="configure-video-geaor">
<div className="popup-contents">
<div classNmae="video-header">
<h2 className="subcaption">video is not configured</h2>
<div className="subcaption">
If you might like to use video in sessions, please select a webcam to use, and a video resolution and frame rate to capture. Then click the TEST WEBCAM button to verify that you see video from your webcam properly. In sessions, you can choose to turn video on or off any time.
</div>
</div>
<div className="webcam-container">
<WebcamViewer isVisible={true} />
</div>
<div className="important-note">
<h5>
Important Note
</h5>
<div className="contents">
You can update your video configuration any time in your Account settings, or in the menus of the video window while in a session.
</div>
</div>
<div className="clearall" />
</div>
<div className="close-behavior">
<span className="field">
<input type="checkbox" name="dont_show" /><label htmlFor="dont_show">Don't show this again</label>
</span>
<a className="button-orange close-link" onClick={this.close}>CLOSE</a>
</div>
</div>`
close: () ->
$root = jQuery(this.getDOMNode())
$dontShow = $root.find('input[name="dont_show"]')
VideoActions.configureVideoPopupClosed($dontShow.is(':checked'))
window.close()
windowUnloaded: () ->
$root = jQuery(this.getDOMNode())
$dontShow = $root.find('input[name="dont_show"]')
VideoActions.howToUseVideoPopupClosed($dontShow.is(':checked'))
componentDidMount: () ->
$(window).unload(@windowUnloaded)
$root = jQuery(this.getDOMNode())
$dontShow = $root.find('input[name="dont_show"]')
context.JK.checkbox($dontShow)
@resizeWindow()
# this is necessary due to whatever the client's rendering behavior is.
setTimeout(@resizeWindow, 300)
componentDidUpdate: () ->
@resizeWindow()
resizeWindow: () =>
$container = $('#minimal-container')
width = $container.width()
height = $container.height()
# there is 20px or so of unused space at the top of the page. can't figure out why it's there. (above #minimal-container)
mysteryTopMargin = 20
# deal with chrome in real browsers
offset = (window.outerHeight - window.innerHeight) + mysteryTopMargin
# handle native client chrome that the above outer-inner doesn't catch
#if navigator.userAgent.indexOf('JamKazam') > -1
#offset += 25
window.resizeTo(width, height + offset)
})

View File

@ -0,0 +1,105 @@
context = window
logger = context.JK.logger
mixins = []
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
accessOpener = false
if window.opener?
try
m = window.opener.MixerStore
accessOpener = true
catch e
if accessOpener
VideoActions = window.opener.VideoActions
VideoStore = window.opener.VideoStore
#mixins.push(Reflux.listenTo(VideoStore, 'onVideoStateChanged'))
@PopupHowToUseVideo = React.createClass({
render: () ->
`<div className="how-to-use-video">
<div className="popup-contents">
<div className="control-holder">
<a className="control start-video" onClick={this.startVideo}>
<span className="helper" />
<img src="/assets/content/webcam-icon-gray.png" width="20" height="20" />
<span id="recording-status">Start Webcam</span>
</a>
</div>
<div className="important-note">
<h5>
Important Note
</h5>
<div className="contents">
You can start and stop your webcam at any time by navigating to the Webcam menu of the video window and selecting Start/Stop Webcam.
</div>
</div>
</div>
<div className="field">
<input type="checkbox" name="dont_show" /><label htmlFor="dont_show">Don't show this again</label>
</div>
<div className="close-behavior">
<a className="button-orange close-link" onClick={this.close}>CLOSE</a>
</div>
</div>`
close: () ->
$root = jQuery(this.getDOMNode())
$dontShow = $root.find('input[name="dont_show"]')
VideoActions.howToUseVideoPopupClosed($dontShow.is(':checked'))
window.close()
startVideo: (e) ->
e.preventDefault
VideoActions.startVideo()
windowUnloaded: () ->
$root = jQuery(this.getDOMNode())
$dontShow = $root.find('input[name="dont_show"]')
VideoActions.howToUseVideoPopupClosed($dontShow.is(':checked'))
componentDidMount: () ->
$(window).unload(@windowUnloaded)
$root = jQuery(this.getDOMNode())
$dontShow = $root.find('input[name="dont_show"]')
context.JK.checkbox($dontShow)
@resizeWindow()
# this is necessary due to whatever the client's rendering behavior is.
setTimeout(@resizeWindow, 300)
componentDidUpdate: () ->
@resizeWindow()
resizeWindow: () =>
$container = $('#minimal-container')
width = $container.width()
height = $container.height()
# there is 20px or so of unused space at the top of the page. can't figure out why it's there. (above #minimal-container)
mysteryTopMargin = 20
# deal with chrome in real browsers
offset = (window.outerHeight - window.innerHeight) + mysteryTopMargin
# handle native client chrome that the above outer-inner doesn't catch
#if navigator.userAgent.indexOf('JamKazam') > -1
#offset += 25
window.resizeTo(width, height + offset)
})

View File

@ -0,0 +1,16 @@
context = window
@VideoActions = Reflux.createActions({
refresh: {}
stopVideo: {}
startVideo: {}
setVideoEncodeResolution: {}
setSendFrameRate: {}
selectDevice: {}
videoWindowOpened : {}
videoWindowClosed : {}
howToUseVideoPopupClosed: {}
toggleVideo: {}
configureVideoPopupClosed: {}
checkPromptConfigureVideo: {}
})

View File

@ -0,0 +1,151 @@
$ = jQuery
context = window
logger = context.JK.logger
EVENTS = context.JK.EVENTS
NAMED_MESSAGES = context.JK.NAMED_MESSAGES
VideoActions = @VideoActions
@VideoStore = Reflux.createStore(
{
listenables: VideoActions
logger: context.JK.logger
videoShared: false
videoOpen : false
state : null
init: ->
this.listenTo(context.SessionStore, this.onSessionChange)
# someone has requested us to refresh our config
onRefresh: ->
currentDevice = context.jamClient.FTUECurrentSelectedVideoDevice()
deviceNames = context.jamClient.FTUEGetVideoCaptureDeviceNames()
#deviceCaps = context.jamClient.FTUEGetVideoCaptureDeviceCapabilities()
currentResolution = context.jamClient.GetCurrentVideoResolution()
currentFrameRate = context.jamClient.GetCurrentVideoFrameRate()
encodeResolutions = context.jamClient.FTUEGetAvailableEncodeVideoResolutions()
frameRates = context.jamClient.FTUEGetSendFrameRates()
#deviceCaps: deviceCaps,
@state = {
currentDevice: currentDevice,
deviceNames: deviceNames,
currentResolution: currentResolution,
currentFrameRate: currentFrameRate,
encodeResolutions: encodeResolutions,
frameRates: frameRates,
videoShared: @videoShared
videoOpen: @videoOpen
}
this.trigger(@state)
onSessionChange: (@session) ->
onStartVideo: ->
if @howtoWindow?
@howtoWindow.close()
@howtoWindow = null
#else # TESTING
# @howtoWindow = window.open("/popups/how-to-use-video", 'How to Use Video', 'scrollbars=yes,toolbar=no,status=no,height=315,width=320')
@logger.debug("SessStartVideoSharing()")
context.jamClient.SessStartVideoSharing(0)
@videoShared = true
@state.videoShared = @videoShared
this.trigger(@state)
onStopVideo: ->
if @videoShared
@logger.debug("SessStopVideoSharing()")
context.jamClient.SessStopVideoSharing()
@videoShared = false
@state.videoShared = @videoShared
this.trigger(@state)
onToggleVideo: () ->
if @videoShared
@onStopVideo()
else
@onStartVideo()
onSetVideoEncodeResolution: (resolution) ->
context.jamClient.FTUESetVideoEncodeResolution(resolution)
onSetSendFrameRate: (frameRates) ->
context.jamClient.FTUESetSendFrameRates(frameRates)
onSelectDevice: (device, caps) ->
result = context.jamClient.FTUESelectVideoCaptureDevice(device, caps)
if(!result)
@logger.error("onSelectDevice failed with device #{device}")
onVideoWindowOpened: () ->
@onRefresh() unless @state?
@logger.debug("in session? #{@session.inSession()}, currentDevice? #{@state?.currentDevice?}, videoShared? #{@videoShared}")
if @session.inSession() && @state.currentDevice? && Object.keys(@state.currentDevice).length > 0 && !@videoShared
context.JK.ModUtils.shouldShow(NAMED_MESSAGES.HOWTO_USE_VIDEO_NOSHOW).done((shouldShow) =>
@logger.debug("checking if user has 'should show' on video howto: #{shouldShow}")
if shouldShow
@howtoWindow = window.open("/popups/how-to-use-video", 'How to Use Video', 'scrollbars=yes,toolbar=no,status=no,height=315,width=320')
)
#@howtoWindo.ParentRecordingStore = context.RecordingStore
#@howtoWindo.ParentIsRecording = @recordingModel.isRecording()
@videoOpen = true
@state.videoOpen = @videoOpen
this.trigger(@state)
onVideoWindowClosed: () ->
@onRefresh() unless @state?
if @howtoWindow?
@howtoWindow.close()
@howtoWindow = null
@videoOpen = false
@state.videoOpen = @videoOpen
@videoShared = false
@state.videoShared = @videoShared
this.trigger(@state)
onHowToUseVideoPopupClosed: (dontShow) ->
if (dontShow)
@logger.debug("requesting that user no longer see how-to-use-video")
context.JK.ModUtils.updateNoShow(NAMED_MESSAGES.HOWTO_USE_VIDEO_NOSHOW);
logger.debug("how-to-use-video popup closed")
@howtoWindow = null
onConfigureVideoPopupClosed: (dontShow) ->
if (dontShow)
@logger.debug("requesting that user no longer see configure-video")
context.JK.ModUtils.updateNoShow(NAMED_MESSAGES.CONFIGURE_VIDEO_NOSHOW);
logger.debug("configure-video popup closed")
@configureWindow = null
# if the user passes all the safeguards, let's see if we should get them to configure video
onCheckPromptConfigureVideo: () ->
@onRefresh() unless @state?
@logger.debug("checkPromptConfigureVideo", @state.currentDevice, @state.deviceNames)
# if no device configured and this is the native client and if you have at least 1 video
if (!@state.currentDevice? || Object.keys(@state.currentDevice).length == 0) && gon?.isNativeClient && Object.keys(@state.deviceNames).length > 0
# and if they haven't said stop bothering me about this
context.JK.ModUtils.shouldShow(NAMED_MESSAGES.CONFIGURE_VIDEO_NOSHOW).done((shouldShow) =>
@logger.debug("checking if user has 'should show' on video config: #{shouldShow}")
if shouldShow
@configureWindow = window.open("/popups/configure-video", 'Configure Video', 'scrollbars=yes,toolbar=no,status=no,height=395,width=444')
)
}
)

View File

@ -0,0 +1,319 @@
context = window
logger = context.JK.logger
reactContext = if window.opener? then window.opener else window
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
if window.opener?
try
m = window.opener.MixerStore
catch e
reactContext = window
VideoStore = reactContext.VideoStore
VideoActions = reactContext.VideoActions
ALERT_NAMES = context.JK.ALERT_NAMES;
BackendToFrontend = {
1 : "CIF (352x288)",
2 : "VGA (640x480)",
3 : "4CIF (704x576)",
4 : "1/2 720p HD (640x360)",
5 : "720p HD (1280x720)",
6 : "1080p HD (1920x1080)"
}
BackendNumericToBackendString = {
1 : "CIF (352X288)",
2 : "VGA (640X480)",
3 : "4CIF (704X576)",
4 : "1/2WHD (640X360)",
5 : "WHD (1280X720)",
6 : "FHD (1920x1080)"
}
BackendToFrontendFPS = {
1: 30,
2: 24,
3: 20,
4: 15,
5: 10
}
FrontendToBackend = {}
for key, value of BackendToFrontend
FrontendToBackend[value] = key
mixins = []
mixins.push(Reflux.listenTo(VideoStore, 'onVideoStateChanged'))
@WebcamViewer = React.createClass({
mixins: mixins
logger: context.JK.logger
getInitialState: () ->
{
currentDevice: null
deviceNames: {}
deviceCaps: null
currentResolution: 0
currentFrameRate: 0
encodeResolutions: {}
frameRates: {}
rescanning: false
}
onVideoStateChanged: (changes) ->
@setState(changes)
render: () ->
if @props.showBackBtn
backBtn = `<a className="hidden button-grey back-btn" onClick={this.back}>BACK</a>`
selectedDevice = this.selectedDeviceName(@state)
# build list of webcams
webcams = []
context._.each @state.deviceNames, (deviceName, deviceGuid) ->
selected = deviceName == selectedDevice
webcams.push `<option key={deviceGuid} value={deviceGuid} selected={selected}>{deviceName}</option>`
# build list of capture resolutions
captureResolutions = []
# load current settings from backend
currentResolution = @state.currentResolution
currentFrameRate = @state.currentFrameRate
# protect against non-video clients pointed at video-enabled server from getting into a session
resolutions = @state.encodeResolutions
frames = @state.frameRates
@logger.debug 'FOUND THESE RESOLUTIONS', resolutions
@logger.debug 'FOUND THESE FPS', frames
context._.each resolutions, (resolution, resolutionKey, obj) =>
#{1: "CIF (352X288)", 2: "VGA (640X480)", 3: "4CIF (704X576)", 4: "1/2WHD (640X360)", 5: "WHD (1280X720)", 6: "FHD (1920x1080)"}
context._.each frames, (frame, key, obj) =>
frontendResolution = BackendToFrontend[resolutionKey]
@logger.error("unknown resolution! #{resolution}", BackendToFrontend) unless frontendResolution
value = "#{resolutionKey}|#{frame}"
text = "#{frontendResolution} at #{frame} fps"
selected = currentResolution + '|' + currentFrameRate == value
captureResolutions.push `<option key={value} value={value} selected={selected}>{text}</option>`
autoSelect = false
if currentResolution == 0
@logger.warn("current resolution not specified; defaulting to VGA")
autoSelect = true
currentResolution = 2
if currentFrameRate == 0
autoSelect = true
@logger.warn("current frame rate not specified; defaulting to 30")
currentFrameRate = 30
else
convertedFrameRate = BackendToFrontendFPS[currentFrameRate]
@logger.debug("translating FPS: backend numeric #{currentFrameRate} to #{convertedFrameRate}")
currentFrameRate = convertedFrameRate
# backend needs to be same as frontend
if autoSelect
@updateBackend(currentResolution, currentFrameRate)
if @state.videoShared
toggleText = 'STOP WEBCAM'
else
toggleText = 'TEST WEBCAM'
if @state.rescanning
rescanning =
`<span className="rescanning-notice">
<span className="spinner-small" />
CHECKING GEAR
</span>`
`<form className="video">
<h2 className="sub-header select-webcam">select webcam:</h2>
<div className="webcam-select-container wizard_control">
<select onChange={this.selectWebcam}>
{webcams}
</select>
</div>
<h2 className="sub-header select-resolution">select video capture resolution & frame rate:</h2>
<div className="webcam-resolution-select-container wizard_control">
<select onChange={this.selectResolution}>
{captureResolutions}
</select>
<a className="ftue-video-settings-help">[?]</a>
</div>
<div className="configure-webcam wizard_control">
{backBtn}
<a className="button-orange webcam-test-btn" onClick={this.toggleWebcam}>{toggleText}</a>
</div>
{rescanning}
</form>`
componentDidMount: () ->
if @props.isVisible
@beforeShow()
$root = $(@getDOMNode())
$videoSettingsHelp = $root.find('.ftue-video-settings-help')
context.JK.helpBubble($videoSettingsHelp, 'ftue-video-settings', {}, {width:300}) if $videoSettingsHelp.length > 0
$videoSettingsHelp.click(false)
componentWillUpdate: (nextProps, nextState) ->
# protect against non-video clients pointed at video-enabled server from getting into a session
@logger.debug("webcam devices", nextState.deviceNames, @state.deviceNames)
if !@initialScan?
@initialScan = true
else
@findChangedWebcams(nextState.deviceNames, @state.deviceNames)
componentWillReceiveProps:(nextProps) ->
if nextProps.isVisible
@beforeShow()
else
@beforeHide()
beforeShow:() ->
VideoActions.refresh()
VideoActions.stopVideo()
context.JK.onBackendEvent(ALERT_NAMES.USB_CONNECTED, 'webcam-viewer', @onUsbDeviceConnected);
context.JK.onBackendEvent(ALERT_NAMES.USB_DISCONNECTED, 'webcam-viewer', @onUsbDeviceDisconnected);
beforeHide: () ->
context.JK.offBackendEvent(ALERT_NAMES.USB_CONNECTED, 'webcam-viewer', @onUsbDeviceConnected);
context.JK.offBackendEvent(ALERT_NAMES.USB_DISCONNECTED, 'webcam-viewer', @onUsbDeviceDisconnected);
if @rescanTimeout?
clearTimeout(@rescanTimeout)
@rescanTimeout = null
@setVideoOff()
onUsbDeviceConnected: () ->
# don't handle USB events when minimized
#return if !context.jamClient.IsFrontendVisible()
logger.debug("USB device connected")
@scheduleRescanSystem(3000)
onUsbDeviceDisconnected:() ->
# don't handle USB events when minimized
#return if !context.jamClient.IsFrontendVisible()
logger.debug("USB device disconnected")
@scheduleRescanSystem(3000)
scheduleRescanSystem: (time) ->
if @rescanTimeout?
clearTimeout(@rescanTimeout)
@rescanTimeout = null
@setState({rescanning: true})
@rescanTimeout = setTimeout(() =>
@setState({rescanning: false})
VideoActions.refresh()
, time)
selectWebcam:(e) ->
e.preventDefault()
device = $(e.target).val()
VideoActions.selectDevice(device, {})
updateBackend: (selectedResolution, selectedFps) ->
@logger.debug 'Selecting webcam resolution: ', selectedResolution
@logger.debug 'Selecting webcam fps: ', selectedFps
VideoActions.setVideoEncodeResolution(selectedResolution)
VideoActions.setSendFrameRate(selectedFps)
selectResolution:(e) ->
e.preventDefault()
resolution = $(e.target).val()
@logger.debug 'new capture resolution selected: ' + resolution
if resolution?
bits = resolution.split('|')
selectedResolution = bits[0]
selectedFps = bits[1]
@updateBackend(selectedResolution, selectedFps)
setVideoOff:() ->
VideoActions.stopVideo()
back: () =>
window.location = '/client#/account'
toggleWebcam:(e) ->
e.preventDefault()
$toggleBtn = $(e.target)
VideoActions.toggleVideo()
#if this.isVideoShared()
# $toggleBtn.removeClass("selected")
# VideoActions.stopVideo()
# @setState({videoShared: false})
#else
# $toggleBtn.addClass("selected")
# VideoActions.startVideo()
# @setState({videoShared: true})
selectedDeviceName:(state) ->
webcamName="None Configured"
# protect against non-video clients pointed at video-enabled server from getting into a session
webcam = state.currentDevice
@logger.debug("currently selected video device", webcam)
if (webcam? && Object.keys(webcam).length>0)
webcamName = Object.keys(webcam)[0]
webcamName
findChangedWebcams: (newList, oldList) ->
newKeys = Object.keys(newList)
oldKeys = Object.keys(oldList)
webcamSelect = $(@getDOMNode()).find('.webcam-select-container select')
if newKeys.length > oldKeys.length
for newKey in newKeys
if oldKeys.indexOf(newKey) == -1
newWebcam = newList[newKey]
@logger.debug("new webcam found: " + newWebcam, newKey)
context.JK.prodBubble(webcamSelect, 'new-webcam-found', {name: newWebcam}, {positions:['right']})
break
else if newKeys.length < oldKeys.length
for oldKey in oldKeys
if newKeys.indexOf(oldKey) == -1
oldWebcam = oldList[oldKey]
@logger.debug("webcam no longer found: " + oldWebcam)
context.JK.prodBubble(webcamSelect, 'old-webcam-lost', {name: oldWebcam}, {positions:['right']})
break
}
)

View File

@ -2,6 +2,7 @@ $ = jQuery
context = window
context.JK ||= {};
ALERT_NAMES = context.JK.ALERT_NAMES;
BackendToFrontend = {

View File

@ -4,21 +4,24 @@
context.JK = context.JK || {}
context.JK.StepVideoGear = function (app, $dialog) {
var $step = null
var $webcamViewer = new context.JK.WebcamViewer()
var $step = null
var webcamViewerReact = null;
function initialize(_$step) {
$step = _$step
$webcamViewer.init($step, false)
var reactElement = React.createElement(window.WebcamViewer, {isVisible: false});
var reactDomNode = $step.find(".webcam-container").get(0)
webcamViewerReact = React.render(reactElement, reactDomNode)
}
function beforeShow() {
$dialog.getWizard().getDialog().find('h1.top-header').text('video gear setup')
$webcamViewer.beforeShow()
webcamViewerReact.beforeShow()
}
function beforeHide() {
$dialog.getWizard().getDialog().find('h1.top-header').text('audio gear setup')
$webcamViewer.beforeHide()
webcamViewerReact.beforeHide()
}
this.beforeShow = beforeShow

View File

@ -73,7 +73,6 @@
.content-wrapper.account-video {
.rescanning-notice {
display:none;
span.spinner-small {
display:inline-block;

View File

@ -319,7 +319,6 @@
.wizard-step.video-gear {
.wizard-step-content .wizard-step-column {
form.video .rescanning-notice {
display:none;
margin-top: 20px;
margin-left: -5px;

View File

@ -0,0 +1,173 @@
@import "client/common";
body.configure-video-popup {
position:relative;
color: $ColorTextTypical;
#minimal-container {
padding-bottom:20px;
}
.popup-contents {
width:350px;
margin-top:20px;
padding-left:44px;
padding-right:44px;
}
.control-holder {
width:100%;
margin: 1em 0;
}
.helper {
display: inline-block;
height: 100%;
vertical-align: middle;
}
.control {
width:231px;
height:34px;
@include border_box_sizing;
margin-top:15px;
padding:3px;
background-color:#242323;
text-align:center;
font-size:13px;
border-radius:5px;
vertical-align:middle;
color:#ccc;
}
.control img {
vertical-align:middle;
margin-right:5px;
}
.control span {
vertical-align:middle;
}
.iradio_minimal {
float:left;
margin-right:5px;
}
label {
padding-top:2px;
}
.field {
height:18px;
&:nth-child(1) {
}
&:nth-child(2) {
margin-top:9px;
}
}
h5 {
text-decoration:underline;
margin-bottom:5px;
}
.important-note {
margin-top:60px;
line-height:150%;
font-size:12px;
}
.close-behavior {
margin-bottom: 10px;
text-align: center;
font-size:11px;
position:relative;
.icheckbox_minimal {
top: 4px;
margin-right: 5px;
}
.field {
position:absolute;
left: 43px;
top: 16px;
}
label {
display:inline;
}
}
.close-link {
margin-top:20px;
font-size:11px;
}
.rescanning-notice {
span.spinner-small {
display:inline-block;
vertical-align: middle;
}
}
h2.subcaption {
color:white;
font-size: 23px;
font-weight: 400;
margin-bottom:20px !important;
}
div.subcaption {
line-height:150%;
font-size:12px;
}
.sub-header {
color:white;
font-size: 16px;
font-weight: 400;
margin-bottom:2px;
}
.webcam-container {
margin-top:40px;
}
.webcam-test-btn {
margin-right: 2px;
margin-top: 4px;
}
select {
@include border_box_sizing;
width:350px;
}
form {
@include border_box_sizing;
width:350px;
}
a.ftue-video-settings-help {
margin-left:15px;
position: absolute;
margin-top: 3px;
}
.configure-webcam {
float:right;
}
.wizard_control {
margin-bottom: 10px;
}
}

View File

@ -0,0 +1,110 @@
@import "client/common";
body.how-to-use-video-popup {
position:relative;
color: $ColorTextTypical;
#minimal-container {
padding-bottom:20px;
}
.popup-contents {
padding-left:44px;
padding-right:44px;
}
.control-holder {
width:100%;
margin: 1em 0;
}
.helper {
display: inline-block;
height: 100%;
vertical-align: middle;
}
.control {
width:231px;
height:34px;
@include border_box_sizing;
margin-top:15px;
padding:3px;
background-color:#242323;
text-align:center;
font-size:13px;
border-radius:5px;
vertical-align:middle;
color:#ccc;
}
.control img {
vertical-align:middle;
margin-right:5px;
margin-top:-2px;
}
.control span {
vertical-align:middle;
}
.iradio_minimal {
float:left;
margin-right:5px;
}
label {
padding-top:2px;
}
.field {
height:18px;
}
.note-show-hide {
font-size:11px;
}
h5 {
text-decoration:underline;
margin-bottom:5px;
}
.important-note {
margin-top:30px;
line-height:150%;
font-size:12px;
}
.field {
margin-top:15px;
font-size:11px;
padding-left:44px;
.icheckbox_minimal {
top: 4px;
margin-right: 5px;
}
label {
display:inline;
}
}
.close-behavior {
position:relative;
margin-top:10px;
margin-bottom:10px;
font-size:11px;
text-align:center;
}
.close-link {
margin-top:20px;
font-size:11px;
}
}

View File

@ -5,6 +5,7 @@
*= require client/screen_common
*= require client/content
*= require client/ftue
*= require client/help
*= require icheck/minimal/minimal
*= require_directory .
*= require client/metronomePlaybackModeSelect

View File

@ -10,6 +10,14 @@ class PopupsController < ApplicationController
render :layout => "minimal"
end
def how_to_use_video
render :layout => "minimal"
end
def configure_video
render :layout => "minimal"
end
def youtube_player
@video_id = params[:id]
render :layout => "minimal"

View File

@ -21,12 +21,11 @@
<div class="video-header">
<h2 class="subcaption">video gear:</h2>
<div class="subcaption">
Select webcam to use for video in sessions. Verify that you see video from webcam in the external application window (it may be behind this window). Configure webcam settings if desired.
Select webcam to use for video in sessions. Verify that you see video from webcam in the external application window (it may be behind this window).
</div>
</div>
<div class="webcam-container">
<%= render 'webcam' %>
</div>
</div>

View File

@ -8,7 +8,7 @@
%li Select video capture settings.
%li Click the Test webcam button to open a window and verify that your webcam is properly capturing video. Then use the Window / Close Video Window menu command to close the window, and click the Next button to move forward.
.wizard-step-column
=render(partial: '/clients/webcam')
.webcam-container
.clearall
/ Webcam from client can't currently be embedded:
/ .wizard-step-column

View File

@ -25,6 +25,7 @@
<%= yield %>
</div>
</div>
<%= render "clients/help" %>
<script type="text/javascript">
$(function () {

View File

@ -0,0 +1,2 @@
- provide(:page_name, 'configure-video-popup popup')
= react_component 'PopupWrapper', {component: 'PopupConfigureVideoGear'}

View File

@ -0,0 +1,2 @@
- provide(:page_name, 'how-to-use-video-popup popup')
= react_component 'PopupWrapper', {component: 'PopupHowToUseVideo'}

View File

@ -145,6 +145,8 @@ SampleApp::Application.routes.draw do
match '/recording-controls', to: 'popups#recording_controls'
match '/media-controls', to: 'popups#media_controls'
match '/youtube/player', to: 'popups#youtube_player'
match '/how-to-use-video', to: 'popups#how_to_use_video'
match '/configure-video', to: 'popups#configure_video'
end
scope '/corp' do