diff --git a/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee index 0964bf602..0460c9819 100644 --- a/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee @@ -7,7 +7,15 @@ MixerActions = @MixerActions mixins: [Reflux.listenTo(@SessionStatsStore,"onStatsChanged")] onStatsChanged: (stats) -> - @setState({stats: stats}) + @setState({stats: stats[@props.participant.client_id]}) + + getInitialState: () -> + stats = window.SessionStatsStore.stats + if stats? + clientStats = stats[@props.participant.client_id] + else + clientStats = null + {stats: clientStats} handleMute: (e) -> e.preventDefault() @@ -53,9 +61,13 @@ MixerActions = @MixerActions WebkitTransform: "rotate(#{pan}deg)" } - connectionState = @state.stats.clientState(this.props.participant.client_id) + classification = @state.stats?.classification + + if !classification? + classification = 'unknown' + connectionStateClasses = { 'track-connection-state': true} - connectionStateClasses[connectionState] = true + connectionStateClasses[classification] = true `
@@ -68,7 +80,7 @@ MixerActions = @MixerActions
-
+

@@ -87,6 +99,7 @@ MixerActions = @MixerActions $root = $(this.getDOMNode()) $mute = $root.find('.track-icon-mute') $pan = $root.find('.track-icon-pan') + $connectionState = $root.find('.track-connection-state') context.JK.interactReactBubble( $mute, @@ -106,6 +119,14 @@ MixerActions = @MixerActions , {width:331, positions:['right', 'left'], offsetParent:$root.closest('.screen')}) + context.JK.interactReactBubble( + $connectionState, + 'SessionStatsHover', + () => + {participant: this.props.participant} + , + {width:380, positions:['right', 'left'], offsetParent:$root.closest('.screen')}) + unless this.props.hasMixer $mute.on("mouseenter", false) $mute.on("mouseleave", false) diff --git a/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee index a2e17c8cc..e2d6ee021 100644 --- a/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee @@ -3,6 +3,21 @@ ChannelGroupIds = context.JK.ChannelGroupIds MixerActions = @MixerActions ptrCount = 0 +StatsInfo = { + system: { + cpu: { + good: "Your computer's processor is not overworked by JamKazam or the your system.", + warn: "Your computer's processor is being heavily used. There is little spare capacity, and this means your processor may not be able to handle all of it's tasks, causing audio quality, latency, and other issues.", + poor: "Your computer's processor is being very heavily used. There is little spare capacity, and this means your processor may not be able to handle all of it's tasks, causing audio quality, latency, and other issues." + }, + network: { + + }, + audio: { + } + } +} + @SessionStatsHover = React.createClass({ propTypes: { @@ -11,16 +26,116 @@ ptrCount = 0 mixins: [Reflux.listenTo(@SessionStatsStore, "onStatsChanged")] - render: () -> - `
+ hover: (type, field) -> + logger.debug("hover! #{type} #{field}") + @setState({hoverType: type, hoverField:field}) + hoverOut: () -> + logger.debug("hover out!") + @setState({hoverType: null, hoverField: null}) + + + stat: (properties, type, name, field, value) -> + classes = {'status-icon': true} + classifier = properties[field + '_level'] + classes[classifier] = true + `
- {name}
{value}
` + + render: () -> + extraInfo = 'Hover over a stat to learn more.' + + if @state.hoverType? + type = @state.hoverType + field = @state.hoverField + + extraInfo = 'No extra info for this metric.' + + classifier = @state.stats?[type]?[field + '_level'] + + if classifier? + info = StatsInfo[type]?[field]?[classifier] + if info? + extraInfo = info + + # "Windows WDM-KS" + + computerStats = [] + networkStats = [] + audioStats = [] + + network = @state.stats?.network + system = @state.stats?.system + audio = @state.stats?.audio + + if system? + if system.cpu? + computerStats.push(@stat(system, 'system', 'Processor', 'cpu', Math.round(system.cpu) + ' %')) + + if audio? + if audio.latency? + audioStats.push(@stat(audio, 'audio', 'Latency', 'latency', audio.latency.toFixed(1) + ' ms')) + if audio.input_jitter? + audioStats.push(@stat(audio, 'audio', 'Input Jitter', 'input_jitter', audio.input_jitter.toFixed(2))) + if audio.output_jitter? + audioStats.push(@stat(audio, 'audio', 'Output Jitter', 'output_jitter', audio.output_jitter.toFixed(2))) + + if network? + + if network.net_bitrate? + networkStats.push(@stat(network, 'network', 'Bandwidth', 'net_bitrate', Math.round(network.net_bitrate) + ' k')) + + if network.ping? + networkStats.push(@stat(network, 'network', 'Latency', 'ping', (network.ping / 2).toFixed(1) + ' ms')) + #if network.jitter? + # networkStats. + if network.pkt_loss? + networkStats.push(@stat(network, 'network', 'Packet Loss', 'pkt_loss', network.pkt_loss.toFixed(1) + ' %')) + + if network.wifi? + if network.wifi + value = 'Wi-Fi' + else + value = 'Ethernet' + networkStats.push(@stat(network, 'network', 'Connectivity', 'wifi', value)) + + + `
+

Session Diagnostics & Stats: {this.props.participant.user.name}

+ +
+
+

Computer

+ {computerStats} +
+
+

Audio Interface

+ {audioStats} +
+
+

Internet

+ {networkStats} +
+
+
+ {extraInfo} +
` onStatsChanged: (stats) -> - @setState({stats: stats}) + stats = window.SessionStatsStore.stats + if stats? + clientStats = stats[@props.participant.client_id] + else + clientStats = null + @setState({stats: clientStats}) getInitialState: () -> - {stats: window.SessionStatsStore.stats} + stats = window.SessionStatsStore.stats + if stats? + clientStats = stats[@props.participant.client_id] + else + clientStats = null + {stats: clientStats, hoverType: null, hoverField: null} closeHover: (e) -> diff --git a/web/app/assets/javascripts/react-components/helpers/ConnectionStatsHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/ConnectionStatsHelper.js.coffee deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee index 70f3e28ed..88fb06be8 100644 --- a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee @@ -13,98 +13,123 @@ SystemThresholds = SessionStatThresholds.system AudioThresholds = SessionStatThresholds.audio @SessionStatsStore = Reflux.createStore( + { + listenables: @SessionStatsActions + rawStats: null - rawStats: null + onPushStats: (stats) -> + @rawStats = stats + @changed() - onPushStats: (stats) -> + classify: (holder, field, threshold) -> + value = holder[field] + fieldLevel = field + '_level' + fieldThreshold = threshold[field] + if value? && fieldThreshold? - logger.debug("connection stats", connectionStats) - @rawStats = stats - @changed() - - classify: (holder, field, threshold) -> - value = holder[field] - fieldLevel = field = '-level' - fieldThreshold = threshold[field] - if value? && fieldThreshold? - - if fieldThreshold.inverse - if value <= threshold.poor - holder[fieldLevel] = 'poor' - else if value <= threshold.warn - holder[fieldLevel] = 'warn' + if fieldThreshold.inverse + if value <= fieldThreshold.poor + holder[fieldLevel] = 'poor' + @participantClassification = 3 + else if value <= fieldThreshold.warn + holder[fieldLevel] = 'warn' + @participantClassification = 2 if @participantClassification == 1 + else + holder[fieldLevel] = 'good' + else if fieldThreshold.eql + if value == fieldThreshold.poor + holder[fieldLevel] = 'poor' + @participantClassification = 3 + else if value == fieldThreshold.warn + holder[fieldLevel] = 'warn' + @participantClassification = 2 if @participantClassification == 1 + else + holder[fieldLevel] = 'good' else - holder[fieldLevel] = 'good' - else if fieldThreshold.eql - if value == threshold.poor - holder[fieldLevel] = 'poor' - else if value == threshold.warn - holder[fieldLevel] = 'warn' - else - holder[fieldLevel] = 'good' - else - if value >= threshold.poor - holder[fieldLevel] = 'poor' - else if value >= threshold.warn - holder[fieldLevel] = 'warn' - else - holder[fieldLevel] = 'good' + if value >= fieldThreshold.poor + holder[fieldLevel] = 'poor' + @participantClassification = 3 + else if value >= fieldThreshold.warn + holder[fieldLevel] = 'warn' + @participantClassification = 2 if @participantClassification == 1 + else + holder[fieldLevel] = 'good' - changed: () -> - for participant in @rawStats + changed: () -> + @stats = {} - # CPU is 0-100 - if participant.cpu? - @classify(participant, 'cpu', SystemThresholds) + console.log("raw stats", @rawStats) + for participant in @rawStats - network = participant.network + @participantClassification = 1 # 1=good, 2=warn, 3=poor - if network? - # audio_bitrate: 256 - # net_bitrate: 286.19244384765625 - # ping: 0.08024691045284271 - # ping_var: 0.6403124332427979 - # pkt_loss: 100 - # wifi: false + # CPU is 0-100 + if participant.cpu? + system = {cpu: participant.cpu} - @classify(network, 'audio_bitrate', NetworkThresholds) - @classify(network, 'ping', NetworkThresholds) - @classify(network, 'pkt_loss', NetworkThresholds) - @classify(network, 'wifi', NetworkThresholds) + @classify(system, 'cpu', SystemThresholds) - audio = participant.audio + participant.system = system - if audio? - # acpu: 5.148329734802246 - # audio_in: "Fast Track" - # audio_in_type: "Core Audio" - # cpu: 22.44668960571289 - # framesize: 2.5 - # in_latency: 5.020833492279053 - # input_iio_jitter: -0.0015926361083984375 - # input_jitter: 0.2977011799812317 - # input_median: 400.16632080078125 - # io_out_latency: "Expected Latency = 9.54 +/- 1.00 ms [Raw/PaBuff/PaRing Latency: 9.54 / 12.04 / 0.00 ms]" - # out_latency: 4.520833492279053 - # output_iio_jitter: -0.07366180419921875 - # output_jitter: 0.40290364623069763 - # output_median: 400.0581970214844 - # output_name: 4 - # samplerate: 48000 + network = participant.network - if audio.in_latency? and audio.out_latency? - audio.latency = in_latency + out_latency + if network? + # audio_bitrate: 256 + # net_bitrate: 286.19244384765625 + # ping: 0.08024691045284271 + # ping_var: 0.6403124332427979 + # pkt_loss: 100 + # wifi: false - @classify(audio, 'audio_in_type', AudioThresholds) - @classify(audio, 'framesize', AudioThresholds) - @classify(audio, 'latency', AudioThresholds) - @classify(audio, 'input_jitter', AudioThresholds) - @classify(audio, 'output_jitter', AudioThresholds) + @classify(network, 'net_bitrate', NetworkThresholds) + @classify(network, 'ping', NetworkThresholds) + @classify(network, 'pkt_loss', NetworkThresholds) + @classify(network, 'wifi', NetworkThresholds) - @stats = @rawStats - @trigger(@stats) + audio = participant.audio + + if audio? + # acpu: 5.148329734802246 + # audio_in: "Fast Track" + # audio_in_type: "Core Audio" + # cpu: 22.44668960571289 + # framesize: 2.5 + # in_latency: 5.020833492279053 + # input_iio_jitter: -0.0015926361083984375 + # input_jitter: 0.2977011799812317 + # input_median: 400.16632080078125 + # io_out_latency: "Expected Latency = 9.54 +/- 1.00 ms [Raw/PaBuff/PaRing Latency: 9.54 / 12.04 / 0.00 ms]" + # out_latency: 4.520833492279053 + # output_iio_jitter: -0.07366180419921875 + # output_jitter: 0.40290364623069763 + # output_median: 400.0581970214844 + # output_name: 4 + # samplerate: 48000 + + if audio.cpu? + system = {cpu: audio.cpu} + @classify(system, 'cpu', SystemThresholds) + participant.system = system + + if audio.in_latency? and audio.out_latency? + audio.latency = audio.in_latency + audio.out_latency + @classify(audio, 'framesize', AudioThresholds) + @classify(audio, 'latency', AudioThresholds) + @classify(audio, 'input_jitter', AudioThresholds) + @classify(audio, 'output_jitter', AudioThresholds) + switch @participantClassification + when 1 then participant.classification = 'good' + when 2 then participant.classification = 'warn' + when 3 then participant.classification = 'poor' + else + participant.classification = 'unknown' + + @stats[participant.id] = participant + + @trigger(@stats) + } ) \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss index bea31605f..61640625c 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss @@ -6,6 +6,7 @@ $session-screen-divider: 1190px; @content; } } + @mixin session-normal { @media (min-width: #{$session-screen-divider}) { @content; @@ -13,97 +14,166 @@ $session-screen-divider: 1190px; } #session-screen { - .session-container { - overflow-x: hidden; + .session-container { + overflow-x: hidden; + } + + .session-track { + @include session-small { + max-width: 120px; } - .session-track { + &.metronome, &.jam-track, &.recorded-track, &.backing-track { @include session-small { - max-width: 120px; + height: auto; } - - &.metronome, &.jam-track, &.recorded-track, &.backing-track { + .track-icon-pan { @include session-small { - height:auto; - } - .track-icon-pan { - @include session-small { - margin-right:0; - } - } - - .track-buttons { - @include session-small { - margin:12px 0 0; - } - } - - table.vu { - @include session-small { - margin-top:5px; - } - } - - .track-controls { - @include session-small { - margin-right:8px; - } - } - - .track-icon-pan { - @include session-small { - margin-right:2px; - } - } - .track-instrument { - @include session-small { - margin: -4px 12px 0 0; - } + margin-right: 0; } } - &.jam-track-category, &.recorded-category { - .track-controls { - @include session-small { - margin-top:5px; - margin-right:8px; - } + + .track-buttons { + @include session-small { + margin: 12px 0 0; } - table.vu { - @include session-small { - margin-top:0; - } + } + + table.vu { + @include session-small { + margin-top: 5px; } - .jam-track-header { - @include session-small { - float:left; - } + } + + .track-controls { + @include session-small { + margin-right: 8px; } - .name { - @include session-small { - float:left; - } + } + + .track-icon-pan { + @include session-small { + margin-right: 2px; } - .track-buttons { - @include session-small { - margin-top:12px; - } + } + .track-instrument { + @include session-small { + margin: -4px 12px 0 0; } } } + &.jam-track-category, &.recorded-category { + .track-controls { + @include session-small { + margin-top: 5px; + margin-right: 8px; + } + } + table.vu { + @include session-small { + margin-top: 0; + } + } + .jam-track-header { + @include session-small { + float: left; + } + } + .name { + @include session-small { + float: left; + } + } + .track-buttons { + @include session-small { + margin-top: 12px; + } + } + } + } - .track-controls { - @include session-small { - margin-top:8px; + .track-controls { + @include session-small { + margin-top: 8px; + } + } + + .track-buttons { + @include session-small { + margin-left: 14px; + } + } + + .stats-info { + float: left; + width: 120px; + border-radius: 15px; + border-color: #fc0; + border-width: 1px; + border-style: solid; + padding:10px; + height: 313px; + margin-top: 20px; + } + .stats-area { + float: left; + padding: 20px; + color: #cccccc; + + h3 { + color: white; + } + + .stat { + margin: 7px 0; + } + + .title { + display: inline-block; + width: 100px; + line-height: 20px; + height: 20px; + vertical-align: middle; + } + + .stats-holder { + margin-top: 20px; + h3 { + margin: 0 0 10px 0; + } + } + .status-icon { + border-radius: 10px; + width: 20px; + height: 20px; + margin-right: 10px; + display: inline-block; + vertical-align: middle; + &.poor { + background-color: $poor; + } + &.warn { + background-color: $fair; + } + &.unknown { + background-color: $unknown; + } + &.good { + background-color: $good; } } - .track-buttons { - @include session-small { - margin-left:14px; - } + .stat-value { + width: 50px; + display: inline-block; + height: 20px; + vertical-align: middle; + line-height: 20px; } - h2 { + } + + h2 { color: #fff; font-weight: 600; font-size: 24px; @@ -125,18 +195,18 @@ $session-screen-divider: 1190px; padding: 10px; height: 100%; margin-bottom: 15px; - color:$ColorTextTypical; - overflow:hidden; - position:relative; + color: $ColorTextTypical; + overflow: hidden; + position: relative; } .session-media-tracks { - width:34%; + width: 34%; } .session-notifications { border-right-width: 0; - display:none; //temp + display: none; //temp } .in-session-controls { @@ -162,20 +232,20 @@ $session-screen-divider: 1190px; a { img, .volume-icon { - vertical-align:top; + vertical-align: top; margin-right: 4px; } span { - vertical-align:middle; + vertical-align: middle; } } .button-grey { - margin:0 5px; + margin: 0 5px; padding: 3px 7px; &.session-leave { - margin-right:10px; + margin-right: 10px; } } } @@ -192,15 +262,15 @@ $session-screen-divider: 1190px; bottom: 0; left: 0; right: 0; - text-align:left; + text-align: left; &.media-options-showing { - top:180px; + top: 180px; } border-right: 1px solid #4c4c4c; - margin-bottom:10px; - margin-top:10px; + margin-bottom: 10px; + margin-top: 10px; } p { @@ -209,57 +279,56 @@ $session-screen-divider: 1190px; } .download-jamtrack { - margin-top:20px; + margin-top: 20px; } - .when-empty { - margin-top:25px; - margin-left:22px; - color:$ColorTextTypical; - overflow:hidden; + margin-top: 25px; + margin-left: 22px; + color: $ColorTextTypical; + overflow: hidden; } .session-track-settings { - height:20px; - cursor:pointer; - padding-bottom:1px; // to line up with SessionOtherTracks - color:$ColorTextTypical; + height: 20px; + cursor: pointer; + padding-bottom: 1px; // to line up with SessionOtherTracks + color: $ColorTextTypical; &:hover { - color:white; + color: white; } span { top: -5px; position: relative; - left:3px; + left: 3px; } } .session-invite-musicians { - height:20px; + height: 20px; cursor: pointer; - color:$ColorTextTypical; + color: $ColorTextTypical; &:hover { - color:white; + color: white; } span { - top:-5px; - position:relative; - left:3px; + top: -5px; + position: relative; + left: 3px; } } .closeAudio, .session-clear-notifications { cursor: pointer; - color:$ColorTextTypical; - height:20px; + color: $ColorTextTypical; + height: 20px; img { - top:-2px + top: -2px } span { top: -5px; @@ -268,115 +337,113 @@ $session-screen-divider: 1190px; } } - - .open-media-file-header, .use-metronome-header { - font-size:14px; - line-height:100%; - margin:0; + font-size: 14px; + line-height: 100%; + margin: 0; img { - position:relative; - top:3px; + position: relative; + top: 3px; } } .open-media-file-header { img { - vertical-align:middle; + vertical-align: middle; } .open-text { - margin-left:5px; - vertical-align:bottom; + margin-left: 5px; + vertical-align: bottom; } } .use-metronome-header { clear: both; a { - color:$ColorTextTypical; + color: $ColorTextTypical; &:hover { text-decoration: underline; - color:white; + color: white; } } } .open-media-file-options { - font-size:14px; + font-size: 14px; margin: 7px 0 0 7px !important; - color:$ColorTextTypical; + color: $ColorTextTypical; li { - margin-bottom:5px !important; - margin-left:38px !important; + margin-bottom: 5px !important; + margin-left: 38px !important; a { text-decoration: none; &:hover { text-decoration: underline; - color:white; + color: white; } - color:$ColorTextTypical; + color: $ColorTextTypical; } } } .open-metronome { - margin-left:5px; + margin-left: 5px; } .media-options { - padding-bottom:10px; + padding-bottom: 10px; } .session-mytracks-notracks p.notice { - font-size:14px; + font-size: 14px; } .session-notification { color: white; background-color: #666666; border-radius: 6px; min-height: 36px; - width:100%; - position:relative; + width: 100%; + position: relative; @include border_box_sizing; - padding:6px; - margin:10px 0; + padding: 6px; + margin: 10px 0; &.has-details { - cursor:pointer; + cursor: pointer; } .msg { - font-size:14px; + font-size: 14px; } .detail { - font-size:12px; - margin-top:5px; + font-size: 12px; + margin-top: 5px; } .notify-help { - color:#ffcc00; + color: #ffcc00; text-decoration: none; - font-size:12px; - margin-left:5px; + font-size: 12px; + margin-left: 5px; &:hover { - text-decoration:underline !important; + text-decoration: underline !important; } } } .close-window { - text-align:center; - clear:both; + text-align: center; + clear: both; } .session-volume-settings .volume-icon { - display:inline-block; - width:14px; - height:14px; - background-image:url('/assets/content/icon_mute_sm.png'); - background-repeat:no-repeat; + display: inline-block; + width: 14px; + height: 14px; + background-image: url('/assets/content/icon_mute_sm.png'); + background-repeat: no-repeat; &.muted { background-position: 0 0; diff --git a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss index 700469329..30cc9f430 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss @@ -126,10 +126,20 @@ cursor:pointer; text-align: center; margin-left:10px; - } - - .circle { + border-radius:10px; + &.poor { + background-color:$poor; + } + &.warn { + background-color:$fair; + } + &.unknown { + background-color:$unknown; + } + &.good { + background-color:$good; + } } .track-icon-equalizer { @@ -370,6 +380,7 @@ } } + &.SessionTrackVolumeHover { .session-track { margin-bottom:0; @@ -381,6 +392,18 @@ height:380px ! important; } + &.SessionStatsHover { + width:380px; + height:400px; + @include border_box_sizing; + + h3 { + margin-top:20px; + color: white; + margin-left:20px; + } + } + &.SessionTrackPanHover { width:331px; height:197px; diff --git a/web/config/application.rb b/web/config/application.rb index 84296bfd3..e36417da7 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -380,7 +380,7 @@ if defined?(Bundler) config.session_stat_thresholds = { network: { wifi: {warn: true, poor: true, eql: true}, - audio_bitrate: {warn: 200, poor: 135, inverse:true}, + net_bitrate: {warn: 210, poor: 155, inverse:true}, ping: {warn: 40, poor: 70}, pkt_loss: {warn: 3, poor: 10} }, @@ -388,7 +388,7 @@ if defined?(Bundler) cpu: {warn: 70, poor:85} }, audio: { - audio_in_type: {warn: 'WDM', poor: 'WDM', eql: true}, + framesize: {warn: 2.6, poor: 2.6}, latency: {warn: 10, poor: 20}, input_jitter: {warn: 0.5, poor: 1},