working on livestream

This commit is contained in:
Seth Call 2017-06-10 15:34:08 -05:00
parent b9f9856541
commit 4d7712093a
26 changed files with 3912 additions and 3004 deletions

View File

@ -4,15 +4,30 @@ module JamRuby
@@log = Logging.logger[Broadcast]
STATUS_COMPLETED = 'completed'
STATUS_COMPLETED = 'complete'
STATUS_ABANDONED = 'abandoned'
STATUS_REVOKED = 'revoked'
STATUS_DELETED = 'deleted'
DONE_STATUSES = [STATUS_COMPLETED, STATUS_ABANDONED, STATUS_REVOKED]
DONE_STATUSES = [STATUS_ABANDONED, STATUS_REVOKED, STATUS_DELETED, STATUS_COMPLETED]
belongs_to :music_session, :class_name => 'JamRuby::MusicSsession'
def self.current_broadcast(music_session)
Broadcast.where(music_session_id: music_session.id).where('broadcast_status not in (?)', Broadcast::DONE_STATUSES).first
end
def self.unlink_broadcast(music_session)
broadcast = current_broadcast(music_session)
if broadcast
broadcast.broadcast_status = STATUS_DELETED
broadcast.save
end
end
# data should be JSON hash from google API
def update_broadcast_data(data)
self.broadcast_status = data["status"]["lifeCycleStatus"]
self.broadcast_data = data.to_json
end
end
end

View File

@ -83,31 +83,40 @@ module JamRuby
Broadcast.current_broadcast(self)
end
def create_broadcast(google_client, user, broadcast_options)
def unlink_broadcast
Broadcast.unlink_broadcast(self)
end
def create_broadcast(user, broadcast_options, google_client = GoogleClient.new)
broadcast = current_broadcast
if broadcast.nil?
broadcast = create_youtube_broadcast(google_client, user, broadcast_options)
broadcast = create_youtube_broadcast(user, broadcast_options, google_client)
else
refresh_youtube_broadcast(google_client, user, broadcast)
result = refresh_youtube_broadcast(user, broadcast, broadcast_options, google_client)
# check against Youtube the real state of broadcast, to see if we need a new one?
if result.nil?
unlink_broadcast # user probably deleted it, or marked it complete.
broadcast = create_youtube_broadcast(user, broadcast_options, google_client)
end
end
broadcast
end
def create_stream(google_client, user, broadcast_options)
def create_stream(user, broadcast_options, google_client = GoogleClient.new)
broadcast = create_broadcast(google_client, user, broadcast_options)
broadcast = create_broadcast(user, broadcast_options, google_client)
stream = current_stream(broadcast)
if stream.nil?
create_youtube_stream(google_client, user, broadcast, broadcast_options)
bind_broadcast(google_client, user, broadcast)
create_youtube_stream(user, broadcast, broadcast_options, google_client)
bind_broadcast(user, broadcast, google_client)
else
bind_broadcast(google_client, user, broadcast)
bind_broadcast(user, broadcast, google_client)
end
end
@ -115,14 +124,66 @@ module JamRuby
broadcast.stream_id
end
def refresh_youtube_broadcast(google_client, user, broadcast)
broadcast_data = google_client.get_broadcast(user, broadcast.broadcast_id)
broadcast.broadcast_status = broadcast_data["status"]["lifeCycleStatus"]
broadcast.broadcast_data = broadcast_data.to_json
def refresh_youtube_broadcast(user, broadcast, broadcast_data = nil, google_client = GoogleClient.new)
if broadcast_data.nil?
broadcast_data = google_client.get_broadcast(user, broadcast.broadcast_id)
end
if broadcast_data
broadcast.update_broadcast_data(broadcast_data)
broadcast.save
true
else
# this path makes sense if the user deleted the video on the server, but we do not yet know it
nil
end
end
def get_livestream(user, google_client = GoogleClient.new)
broadcast = current_broadcast
if broadcast.nil?
nil
else
stream_id = current_stream(broadcast)
if stream_id.nil?
nil
else
return google_client.get_livestream(user, stream_id)
end
end
end
def get_broadcast(user, google_client = UserManager.new.get_google_client)
broadcast = current_broadcast
if broadcast.nil?
nil
else
broadcast_data = google_client.get_broadcast(user, broadcast.broadcast_id)
broadcast.update_broadcast_data(broadcast_data)
broadcast.save
broadcast
end
end
def set_livestream_live(user, google_client = GoogleClient.new)
livestream = get_livestream(user, google_client)
if livestream
if livestream["status"]["streamStatus"] == "active"
transition_broadcast(user, broadcast, 'live', google_client)
end
else
end
end
# https://developers.google.com/youtube/v3/live/docs/liveStreams#resource
def create_youtube_stream(google_client, user, broadcast, broadcast_options)
def create_youtube_stream(user, broadcast, broadcast_options, google_client = GoogleClient.new)
# https://developers.google.com/youtube/v3/live/docs/liveStreams/insert
# required
@ -133,7 +194,7 @@ module JamRuby
stream_options = {}
stream_options[:snippet] ||= {}
stream_options[:snippet][:title] ||= name
stream_options[:snippet][:isDefaultStream] = false
stream_options[:snippet][:isDefaultStream] ||= false
#broadcast_options[:snippet][:scheduledEndTime] = end_time.utc.iso8601
stream_options[:cdn] ||= {}
@ -142,7 +203,9 @@ module JamRuby
stream_options[:cdn][:ingestionType] ||= 'rtmp'
stream_options[:contentDetails] ||= {}
stream_options[:contentDetails][:isReusable] = false
stream_options[:contentDetails][:isReusable] ||= false
stream_options[:contentDetails][:monitorStream] ||= {}
stream_options[:contentDetails][:monitorStream][:enableMonitorStream] ||= false
stream_options = google_client.create_stream(user, stream_options)
@ -155,7 +218,7 @@ module JamRuby
broadcast
end
def create_youtube_broadcast(google_client, user, broadcast_options)
def create_youtube_broadcast(user, broadcast_options, google_client = GoogleClient.new)
start_time, end_time = youtube_times
broadcast_options ||= {}
@ -181,16 +244,24 @@ module JamRuby
broadcast.music_session_id = self.id
broadcast.user_id = user.id
broadcast.broadcast_id = broadcast_data["id"]
broadcast.broadcast_status = broadcast_data["status"]["lifeCycleStatus"]
broadcast.broadcast_data = broadcast_data.to_json
broadcast.update_broadcast_data(broadcast_data)
broadcast.save!
broadcast
end
def bind_broadcast(google_client, user, broadcast)
def bind_broadcast(user, broadcast, google_client = GoogleClient.new)
bind_data = google_client.bind_broadcast(user, broadcast.broadcast_id, broadcast.stream_id)
broadcast.broadcast_data = bind_data.to_json
broadcast.update_broadcast_data(bind_data)
broadcast.save!
broadcast
end
# broadcastStatus one of complete, live, testing
def transition_broadcast(user, broadcast, broadcastStatus, google_client = GoogleClient.new)
bind_data = google_client.transition_broadcast(user, broadcast.broadcast_id, broadcastStatus)
broadcast.update_broadcast_data(bind_data)
broadcast.save!
broadcast
end

View File

@ -1752,7 +1752,6 @@
this.isSessVideoShared = isSessVideoShared;
this.SessStopVideoSharing = SessStopVideoSharing;
this.SessStartVideoSharing = SessStartVideoSharing;
this.getOpenVideoSources = getOpenVideoSources;
// Clipboard
this.SaveToClipboard = SaveToClipboard;

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,7 @@
//= require ./react-components/stores/BrowserMediaStore
//= require ./react-components/stores/RecordingStore
//= require ./react-components/stores/VideoStore
//= require ./react-components/stores/VideoLiveStreamStore
//= require ./react-components/stores/SessionStore
//= require ./react-components/stores/SessionStatsStore
//= require ./react-components/stores/BroadcastStore

View File

@ -0,0 +1,187 @@
context = window
logger = context.JK.logger
rest = context.JK.Rest()
mixins = []
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
# this check ensures we attempt to listen if this component is created in a popup
reactContext = if window.opener? then window.opener else window
accessOpener = false
if window.opener?
try
m = window.opener.MixerStore
accessOpener = true
catch e
reactContext = window
VideoLiveStreamStore = reactContext.VideoLiveStreamStore
VideoLiveStreamActions = reactContext.VideoLiveStreamActions
AppActions = reactContext.AppActions
if accessOpener
mixins.push(Reflux.listenTo(VideoLiveStreamStore,"onVideoLiveStreamChanged"))
@PopupVideoLiveStream = React.createClass({
mixins: mixins
onVideoLiveStreamChanged: (videoLiveStream) ->
if @unloaded
return
logger.debug("onVideoLiveStreamChanged", videoLiveStream)
this.setState({videoLiveStream: videoLiveStream})
render: () ->
# there are a few initial states: if auth is null, we don't know yet (and are checking against the server)
# if auth is set, then we can show the upload btn
# and if auth == false, then we need the user to try and log in
if @state.auth == false
action = `<input className="google_login_button" type='image' src="/assets/google_signin.png" height="30px" onClick={this.startGoogleLogin}/>`
instructions = `<p>To upload this recording to YouTube, you must give JamKazam the necessary access to your YouTube account by clicking the button below.</p>`
else if @state.videoLiveStream
videoLiveStream = @state.videoLiveStream
if videoLiveStream.broadcast?
controlsUrl = "https://www.youtube.com/live_event_analytics?v=" + videoLiveStream.broadcast.id
videoUrl = "https://www.youtube.com/watch?v=" + videoLiveStream.broadcast.id
controlLink = `<a onClick={this.watchVideo} className="video-url" href={controlsUrl}>Open Broadcast Controls</a>`
videoLink = `<a onClick={this.watchVideo} className="video-url" href={videoUrl}>Open Video Page</a>`
if videoLiveStream.errorMessage
action = null
instructions = `<p>The stream setup failed. Reason: ({videoLiveStream.errorMessage})</p>`
else if @state.auth == false
action = `<input className="google_login_button" type='image' src="/assets/google_signin.png" height="30px" onClick={this.startGoogleLogin}/>`
instructions = `<p>To upload this recording to YouTube, you must give JamKazam the necessary access to your YouTube account by clicking the button below.</p>`
else if videoLiveStream.streaming
action = `<div><a className="button-orange close-btn" onClick={this.onCloseRequested}>STOP LIVECASTING</a></div>`
instructions = `<p>{videoLink}{controlLink}</p>`
else if videoLiveStream.creatingBroadcast
action = null
instructions = `<p>Synchronizing with YouTube</p>`
else if videoLiveStream.waitingOnReady
action = null
instructions = `<p>Synchronizing with YouTube ... waiting on stream setup</p>`
else if videoLiveStream.waitingOnTesting || videoLiveStream.transitioningTesting
action = null
instructions = `<p>Synchronizing with YouTube ... stream is ready ... skipping preview</p>`
else if videoLiveStream.waitingOnLive
action = null
instructions = `<p>Synchronizing with YouTube ... stream is ready ... waiting to go live</p>`
else if !@state.initiated
action = `<a onClick={this.startBroadcasting} className="start-btn button-orange" href='#'>START BROADCASTING</a>`
if videoLiveStream.broadcast
instructions = `<p>Click 'Start Broadcasting' to setup a live streaming session on YouTube.</p>`
else
instructions = `<p>Press 'Start Broadcasting' to start broadcasting on YouTube.</p>`
else if @state.initiated
instructions = `<p>Streaming initiated. Please wait...</p>`
else
name = null
action = null
instructions = `<p>UNKNOWN STATE: {JSON.stringify(videoLiveStream)}</p>`
else
name = 'Loading...'
action = null
`<div className="video-live-stream">
<h3>Live Stream Video</h3>
{instructions}
<div className="control-holder">
{action}
</div>
</div>`
getInitialState: () ->
{auth: null, initiated: false}
startBroadcasting: (e) ->
e.preventDefault()
VideoLiveStreamActions.startLiveStreaming()
@setState({initiated: true})
watchVideo: (e) ->
e.preventDefault()
$link = $(e.target)
AppActions.openExternalUrl($link.attr('href'))
onCloseRequested: (e) ->
e.preventDefault()
window.close()
startGoogleLogin: (e) ->
e.preventDefault()
logger.debug("Starting google login")
window._oauth_win = window.open("/auth/google_login", "Log In to Google", "height=700,width=500,menubar=no,resizable=no,status=no");
window._oauth_callback = @oauthCallback
oauthCallback: () ->
window._oauth_win.close()
@checkAuth()
checkAuth:() ->
rest.getGoogleAuth()
.done((auth) =>
if auth.auth?
@setState({auth: auth.auth})
else
@setState({auth: false})
)
.fail(() =>
@setState({errorReason: 'Unable to fetch user authorization'})
)
componentDidMount: () ->
$(window).unload(@windowUnloaded)
@checkAuth()
VideoLiveStreamActions.loadBroadcast(gon.session_id)
@resizeWindow()
# this is necessary due to whatever the client's rendering behavior is.
setTimeout(@resizeWindow, 300)
shouldComponentUpdate: () ->
return !@unloaded
componentDidUpdate: () ->
@resizeWindow()
setTimeout(@resizeWindow, 1000)
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
mysteryTopMargin = 0
# 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
width = 100 if width < 100
height = 100 if height < 100
window.resizeTo(width, height + offset)
windowUnloaded: () ->
@unloaded = true
window.unloaded = true
VideoLiveStreamActions.popupClosed()
})

View File

@ -15,4 +15,5 @@ context = window
checkPromptConfigureVideo: {}
setVideoEnabled: {}
refreshVideoState: {}
startLiveStream: {}
})

View File

@ -0,0 +1,9 @@
context = window
@VideoLiveStreamActions = Reflux.createActions({
startLiveStream: {}
streamStarted: {}
popupClosed: {}
loadBroadcast: {}
startLiveStreaming: {}
})

View File

@ -4,6 +4,7 @@ logger = context.JK.logger
RecordingActions = @RecordingActions
SessionActions = @SessionActions
JamBlasterActions = @JamBlasterActions
VideoLiveStreamActions = @VideoLiveStreamActions
#window.StupidCallback: () =>
# alert("STUPID CALLBACK")
@ -24,6 +25,8 @@ JamBlasterActions = @JamBlasterActions
console.log("GENERIC CALLBACK CALLED: ", map)
if map.cmd == 'join_session'
SessionActions.joinSession(map['music_session_id'])
else if map.cmd == 'start_livestream'
VideoLiveStreamActions.startLiveStream()
else if map.cmd == 'client_pair_state'
JamBlasterActions.pairState(map)
else if map.cmd == 'jamblaster_tracks_updated'
@ -31,6 +34,5 @@ JamBlasterActions = @JamBlasterActions
else if map.cmd == 'file_xfer_from_parent'
if map.filename && map.filename.indexOf('RT-mix.wav') > -1
RecordingActions.mixTransferred()
}
)

View File

@ -0,0 +1,265 @@
context = window
logger = context.JK.logger
rest = context.JK.Rest()
EVENTS = context.JK.EVENTS
NAMED_MESSAGES = context.JK.NAMED_MESSAGES
VideoLiveStreamActions = @VideoLiveStreamActions
@VideoLiveStreamStore = Reflux.createStore(
{
listenables: VideoLiveStreamActions
logger: context.JK.logger
creatingBroadcast: false
init: ->
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (@app) ->
onPopupClosed: () ->
if @childWindow?
@childWindow = null
logger.debug("Popup closed")
context.jamClient.StopLiveStreaming()
@streaming = false
@onRefresh()
onRefresh: () ->
@state = {
creatingBroadcast: @creatingBroadcast,
createdStream: @createdStream,
createStreamError: @createStreamError,
waitingForReady: @waitingForReady,
waitingOnTesting: @waitingOnTesting,
waitingOnLive: @waitingOnLive,
transitioningTesting: @transitioningTesting,
streaming: @streaming,
errorMessage: @errorMessage,
broadcast: @broadcast
}
this.trigger(@state)
onStartLiveStream: () ->
@creatingBroadcast = false
@createdStream = false
@createStreamError = false
@transitioningTesting = false
@waitingForReady = false
@waitingOnLive = false
@waitingOnTesting = false
@errorMessage = null
@streaming = false
@logger.debug("onStartLiveStream")
@onRefresh()
if @childWindow?
logger.debug("show live stream popup being closed automatically")
@childWindow.close()
@childWindow = null
@childWindow = window.open("/popups/video/stream/" + context.SessionStore.id(), 'Video Live Stream', 'scrollbars=yes,toolbar=no,status=no,height=155,width=350')
startLiveStreaming: () ->
@logger.debug("user requests live stream")
@createLiveStream()
# do not use; here until can remove
startGoogleLogin: (e) ->
@logger.debug("Starting google login")
window._oauth_win = window.open("/auth/google_login", "Log In to Google", "height=700,width=500,menubar=no,resizable=no,status=no");
window._oauth_callback = @oauthCallback
oauthCallback: () ->
@creatingBroadcast = true
@logger.debug("oauthCallback... checking auth")
window._oauth_win.close()
@checkAuth()
checkAuth:() ->
rest.getGoogleAuth()
.done((auth) =>
@logger.debug("check Auth is done", auth)
if auth.auth?
@createLiveStream()
else
@creatingBroadcast = false
@onRefresh()
# alert to user
)
.fail(() =>
@creatingBroadcast = false
@onRefresh()
# lert to user
)
loadBroadcast: (sessionId) ->
@broadcast = null
@onRefresh()
@fetchBroadcast(sessionId)
fetchBroadcast: (sessionId) ->
rest.getLiveStream(sessionId)
.done((broadcast) =>
@broadcast = broadcast
@onRefresh()
)
.fail((jqXHR) =>
logger.error("unabe to fetch broadcast", jqXHR.responseText)
@onRefresh()
)
createLiveStream: () ->
@creatingBroadcast = true
rest.createLiveStream(context.SessionStore.id())
.done((broadcast) =>
@creatingBroadcast = false
@onRefresh()
success = context.jamClient.StartLiveStreaming(broadcast.stream_name)
if success
@createdStream = true
@waitingForReady = true
@transitionTimeout = new Date().getTime() + 10000 # die in 10 seconds
@onRefresh()
setTimeout(() =>
@waitForReady()
, 1000)
else
@createStreamError = true
@onRefresh()
)
.fail(() =>
@creatingBroadcast = false
@onRefresh()
)
waitForReady: () ->
rest.getLiveStream(context.SessionStore.id())
.done((broadcast) =>
if broadcast.broadcast_status == 'ready'
@waitingForReady = false
@transitionTesting()
else
if new Date().getTime() > @transitionTimeout
# uh oh. waited fo ra while; stream never became ready
@errorMessage = 'YouTube never indicated stream is ready for testing'
@waitingForReady = false
@onRefresh()
else
setTimeout(() =>
@waitForReady()
, 1000)
)
.fail(() =>
@waitingForReady = false
@errorMessage = 'Could not check status of YouTube broadcast'
context.jamClient.StopLiveStreaming()
@streaming = false
@onRefresh()
)
transitionTesting: () ->
@transitioningTesting = true
@onRefresh()
rest.liveStreamTransition(context.SessionStore.id(), 'testing')
.done((broadcast) =>
@transitioningTesting = false
@waitingOnTesting = true
@transitionTimeout = new Date().getTime() + 20000 # die in 20 seconds
setTimeout(() =>
@waitForTesting()
, 1000)
)
.fail(() =>
@transitioningTesting = false
@errorMessage = 'Could not transition live stream to "testing"'
context.jamClient.StopLiveStreaming()
@streaming = false
@onRefresh()
)
transitionLive: () ->
@transitioningLive = true
@onRefresh()
rest.liveStreamTransition(context.SessionStore.id(), 'live')
.done((broadcast) =>
@transitioningLive = false
@waitingOnLive = true
@transitionTimeout = new Date().getTime() + 20000 # die in 20 seconds
setTimeout(() =>
@waitForLive()
, 1000)
)
.fail(() =>
@transitioningLive = false
@errorMessage = 'Could not transition live stream to "live"'
context.jamClient.StopLiveStreaming()
@streaming = false
@onRefresh()
)
waitForTesting: () ->
rest.getBroadcast(context.SessionStore.id())
.done((broadcast) =>
if broadcast.broadcast_status == 'testing'
@waitingOnTesting = false
@transitionLive()
else
if new Date().getTime() > @transitionTimeout
# uh oh. waited fo ra while; stream never became ready
@errorMessage = 'YouTube never indicated stream converted to testing'
@waitingOnTesting = false
@onRefresh()
else
setTimeout(() =>
@waitForTesting()
, 1000)
)
.fail(() =>
@waitingForTesting = false
@errorMessage = 'Could not check status of YouTube broadcast'
context.jamClient.StopLiveStreaming()
@streaming = false
@onRefresh()
)
waitForLive: () ->
rest.getBroadcast(context.SessionStore.id())
.done((broadcast) =>
if broadcast.broadcast_status == 'live'
@waitingOnLive = false
@streaming = true
console.log("BROADCAST TIME", broadcast)
@onRefresh()
else
if new Date().getTime() > @transitionTimeout
# uh oh. waited fo ra while; stream never became ready
@errorMessage = 'YouTube never indicated stream converted to live'
@waitingOnLive = false
@onRefresh()
else
setTimeout(() =>
@waitForLive()
, 1000)
)
.fail(() =>
@waitingForLive = false
@errorMessage = 'Could not check status of YouTube broadcast'
context.jamClient.StopLiveStreaming()
@streaming = false
@onRefresh()
)
}
)

View File

@ -1,6 +1,7 @@
$ = jQuery
context = window
logger = context.JK.logger
rest = context.JK.Rest()
EVENTS = context.JK.EVENTS
NAMED_MESSAGES = context.JK.NAMED_MESSAGES
@ -236,5 +237,6 @@ BackendToFrontendFPS = {
isVideoEnabled:() ->
return @videoEnabled
}
)

View File

@ -0,0 +1,57 @@
@import "client/common";
#live-streaming-dialog {
width: 600px;
h2 {
color:white;
margin-bottom:10px;
font-size:16px;
}
.dialog-inner {
width: auto;
height:calc(100% - 29px);
padding-bottom:75px;
}
.field {
margin-bottom:10px;
}
input {
display:inline-block;
}
label {
display:inline-block;
}
.iradio_minimal {
display:inline-block;
margin-right: 5px;
top: 4px;
}
div[data-react-class="LiveStreamingDialog"] {
}
.actions {
position:absolute;
right:0;
bottom:0;
margin:0 20px 30px 0;
}
.countdown-msg {
margin-top:30px;
}
.countdown {
color:white;
font-size:18px;
font-weight:bold;
padding:0 10px;
width:30px;
display:inline-block;
text-align:center;
}
.message {
margin-top:20px;
}
}

View File

@ -0,0 +1,58 @@
@import "client/common";
body.video-live-stream{
position: relative;
color: $ColorTextTypical;
#minimal-container {
padding-bottom: 20px;
height:240px;
}
.video-uploader {
padding-left: 30px;
padding-right:30px;
}
h3 {
margin-top:20px;
font-size:16px;
font-weight:bold;
margin-bottom:20px;
text-align:center;
line-height:125%;
}
.control-holder {
margin: 20px 0 20px;
text-align:center;
padding-bottom:20px;
position: absolute;
bottom: 0;
width: 100%;
left: 0;
}
.progress-bar {
background-color:#ED3618;
border:solid 1px #000;
height:20px;
display:block;
@include border_box_sizing;
margin:20px 0;
position:relative;
}
.percentage-progress {
position:absolute;
right:-32px;
}
.video-url {
text-align:center;
display:block;
margin:20px 0;
}
}

View File

@ -7,7 +7,7 @@ body.video-stream {
#minimal-container {
padding-bottom: 20px;
height:240px;
height:170px;
}
.video-stream {
@ -55,4 +55,8 @@ body.video-stream {
display:block;
margin:20px 0;
}
.video-live-stream {
padding:0 20px;
}
}

View File

@ -5,6 +5,7 @@ class ApiMusicSessionsController < ApiController
# have to be signed in currently to see this screen
before_filter :api_signed_in_user, :except => [ :add_like, :show, :show_history, :add_session_info_comment ]
before_filter :lookup_session, only: [:show, :update, :delete, :claimed_recording_start, :claimed_recording_stop, :track_sync, :jam_track_open, :jam_track_close, :backing_track_open, :backing_track_close, :metronome_open, :metronome_close]
before_filter :lookup_perm_session, only: [:get_livestream, :create_livestream, :livestream_transition]
skip_before_filter :api_signed_in_user, only: [:perf_upload]
around_filter :transactions_filter, only:[:sms_index, :ams_index]
@ -648,10 +649,51 @@ class ApiMusicSessionsController < ApiController
respond_with_model(@music_session)
end
private
def get_livestream
unless @music_session.active_music_session.users.exists?(current_user.id)
raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
end
@livestream = @music_session.get_broadcast(current_user)
if @livestream
respond_with (@livestream), responder: ApiResponder
else
render :json => { :message => "No broadcast associated with this session" }, :status => 404
end
end
def create_livestream
unless @music_session.active_music_session.users.exists?(current_user.id)
raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
end
@livestream = @music_session.create_stream(current_user, params[:options])
respond_with (@livestream), responder: ApiResponder
end
def livestream_transition
unless @music_session.active_music_session.users.exists?(current_user.id)
raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
end
broadcast = @music_session.music_session.current_broadcast
if broadcast
@livestream = @music_session.transition_broadcast(current_user, broadcast, params[:broadcastStatus])
respond_with (@livestream), responder: ApiResponder
else
render :json => { :message => "No broadcast associated with this session" }, :status => 404
end
end
private
def lookup_session
@music_session = ActiveMusicSession.find(params[:id])
end
def lookup_perm_session
@music_session = MusicSession.find(params[:id])
end
end

View File

@ -30,6 +30,12 @@ class PopupsController < ApplicationController
end
def video_stream
@session_id = params[:session_id]
gon.session_id= @session_id
render :layout => "minimal"
end
def video_stream
@session_id = params[:session_id]
gon.session_id= @session_id

View File

@ -0,0 +1,3 @@
object @livestream
extends "api_music_sessions/livestream_show"

View File

@ -0,0 +1,3 @@
object @livestream
extends "api_music_sessions/livestream_show"

View File

@ -0,0 +1,3 @@
object @livestream
attributes :id, :broadcast_status, :stream_status, :stream_name, :stream_address, :broadcast_data, :broadcast_id, :stream_id

View File

@ -0,0 +1,3 @@
object @livestream
extends "api_music_sessions/livestream_show"

View File

@ -0,0 +1,2 @@
.dialog.dialog-overlay-sm.top-parent layout='dialog' layout-id='jamblaster-pairing-dialog' id='jamblaster-pairing-dialog'
= react_component 'JamBlasterPairingDialog', {}

View File

@ -1,3 +1,3 @@
- provide(:page_name, 'video-stream popup')
- provide(:title, 'Video Stream')
= react_component 'PopupVideoStreamer', {}
= react_component 'PopupVideoLiveStream', {}

View File

@ -251,6 +251,10 @@ Rails.application.routes.draw do
match '/sessions/:id/metronome/close' => 'api_music_sessions#metronome_close', :via => :post
match '/sessions/:id/session_controller' => 'api_music_sessions#session_controller', :via => :post
match '/sessions/:id/livestream' => 'api_music_sessions#get_livestream', :via => :get
match '/sessions/:id/livestream' => 'api_music_sessions#create_livestream', :via => :post
match '/sessions/:id/livestream/transition' => 'api_music_sessions#livestream_transition', :via => :post
# music session tracks
match '/sessions/:id/tracks' => 'api_music_sessions#track_create', :via => :post
match '/sessions/:id/tracks' => 'api_music_sessions#track_sync', :via => :put

View File

@ -178,6 +178,29 @@ module JamRuby
end
end
def transition_broadcast(user, broadcast_id, broadcastStatus)
auth = UserAuthorization.google_auth(user).first
if auth.nil? || auth.token.nil?
raise JamPermissionError, "No current google token found for user #{user}"
end
begin
my_client = create_client
my_client.authorization = create_authorization(auth, 'https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.force-ssl', true)
#y = my_client.discovered_api('youtube', 'v3')
response = my_client.execute!(:api_method => youtube.live_broadcasts.transition,
:parameters => {:part => 'id,contentDetails,status,snippet', :id => broadcast_id, :broadcastStatus => broadcastStatus })
body = JSON.parse(response.body)
puts "TRANSITION RESPONSE: #{body}"
return body
rescue Google::APIClient::ClientError => e
puts e.result.body
raise e
end
end
def get_broadcast(user, broadcast_id)
auth = UserAuthorization.google_auth(user).first
if auth.nil? || auth.token.nil?
@ -193,8 +216,39 @@ module JamRuby
:parameters => {:part => 'id,contentDetails,status,snippet', :id => broadcast_id })
body = JSON.parse(response.body)
puts "BIND RESPONSE: #{body}"
return body["items"][0] # returns array of items. meh
puts "GET BROADCAST RESPONSE: #{body}"
if body["items"].length == 0
nil
else
body["items"][0] # returns array of items. meh
end
rescue Google::APIClient::ClientError => e
puts e.result.body
raise e
end
end
def get_livestream(user, stream_id)
auth = UserAuthorization.google_auth(user).first
if auth.nil? || auth.token.nil?
raise JamPermissionError, "No current google token found for user #{user}"
end
begin
my_client = create_client
my_client.authorization = create_authorization(auth, 'https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.force-ssl', true)
#y = my_client.discovered_api('youtube', 'v3')
response = my_client.execute!(:api_method => youtube.live_streams.list,
:parameters => {:part => 'id,snippet,cdn,status', :id => stream_id })
body = JSON.parse(response.body)
puts "GET LIVE STREAM RESPONSE: #{body}"
if body["items"].length == 0
nil
else
body["items"][0] # returns array of items. meh
end
rescue Google::APIClient::ClientError => e
puts e.result.body
raise e

View File

@ -11,4 +11,76 @@ namespace :google do
puts broadcast.inspect
end
task get_livestream: :environment do |task, args|
google_client = JamRuby::GoogleClient.new
music_session = MusicSession.first
user = User.find_by_email('seth@jamkazam.com')
livestream = music_session.get_livestream(google_client, user)
puts livestream.inspect
# example:
#{"kind"=>"youtube#liveStream",
# "etag"=>"\"m2yskBQFythfE4irbTIeOgYYfBU/fq4yPJsfCN91Qi2Dd9VuLZ3sm9U\"",
# "id"=>"lgXe2spB8uVk-U6H_b8aSQ1495983496676422",
# "snippet"=>{"publishedAt"=>"2017-05-28T14:58:16.000Z",
# "channelId"=>"UClgXe2spB8uVk-U6H_b8aSQ",
# "title"=>"Private Test Session", "description"=>"", "isDefaultStream"=>false},
# "cdn"=>{"format"=>"360p", "ingestionType"=>"rtmp", "ingestionInfo"=>{"streamName"=>"6axs-3zpj-0esg-d7uv", "ingestionAddress"=>"rtmp://a.rtmp.youtube.com/live2", "backupIngestionAddress"=>"rtmp://b.rtmp.youtube.com/live2?backup=1"}, "resolution"=>"360p", "frameRate"=>"30fps"},
# "status"=>{"streamStatus"=>"active", "healthStatus"=>{"status"=>"good", "configurationIssues"=>[{"type"=>"audioBitrateHigh", "severity"=>"info", "reason"=>"Check audio settings", "description"=>"The audio stream's current bitrate of 255.00 Kbps is higher than the recommended bitrate. We recommend that you use an audio stream bitrate of 128 Kbps."}, {"type"=>"bitrateHigh", "severity"=>"info", "reason"=>"Check video settings", "description"=>"The stream's current bitrate (1286.00 Kbps) is higher than the recommended bitrate. We recommend that you use a stream bitrate of 750 Kbps."}]}}}
end
task get_broadcast: :environment do |task, args|
google_client = JamRuby::GoogleClient.new
music_session = MusicSession.first
user = User.find_by_email('seth@jamkazam.com')
broadcast = music_session.get_broadcast(google_client, user)
# example:
#{"kind"=>"youtube#liveBroadcast",
# "etag"=>"\"m2yskBQFythfE4irbTIeOgYYfBU/7ARgOe0WJLQdOvjeISlnBnnK3L0\"",
# "id"=>"4CLE1CXfKB8",
# "snippet"=>{"publishedAt"=>"2017-05-28T14:58:16.000Z", "channelId"=>"UClgXe2spB8uVk-U6H_b8aSQ", "title"=>"Private Test Session", "description"=>"Private session set up just to test things out in the session interface by myself.", "thumbnails"=>{"default"=>{"url"=>"https://i9.ytimg.com/vi/4CLE1CXfKB8/default_live.jpg?sqp=CMzXq8kF&rs=AOn4CLCo_lxnPzD52m7nQgJ7xHsyRSBLkw", "width"=>120, "height"=>90}, "medium"=>{"url"=>"https://i9.ytimg.com/vi/4CLE1CXfKB8/mqdefault_live.jpg?sqp=CMzXq8kF&rs=AOn4CLDF5tDY7i0H__d27ZTdSfbvrpgc6w", "width"=>320, "height"=>180}, "high"=>{"url"=>"https://i9.ytimg.com/vi/4CLE1CXfKB8/hqdefault_live.jpg?sqp=CMzXq8kF&rs=AOn4CLAl1XUfz8faQDBXEydsDG9D5iRe6g", "width"=>480, "height"=>360}}, "scheduledStartTime"=>"2017-05-28T14:58:17.000Z", "isDefaultBroadcast"=>false, "liveChatId"=>"Cg0KCzRDTEUxQ1hmS0I4"},
# "status"=>{"lifeCycleStatus"=>"testing", "privacyStatus"=>"private", "recordingStatus"=>"notRecording"},
# "contentDetails"=>{"boundStreamId"=>"lgXe2spB8uVk-U6H_b8aSQ1495983496676422", "boundStreamLastUpdateTimeMs"=>"2017-05-28T14:58:16.695Z", "monitorStream"=>{"enableMonitorStream"=>true, "broadcastStreamDelayMs"=>0, "embedHtml"=>"<iframe width=\"425\" height=\"344\" src=\"https://www.youtube.com/embed/4CLE1CXfKB8?autoplay=1&livemonitor=1\" frameborder=\"0\" allowfullscreen></iframe>"}, "enableEmbed"=>true, "enableDvr"=>true, "enableContentEncryption"=>false, "startWithSlate"=>false, "recordFromStart"=>true, "enableClosedCaptions"=>false, "closedCaptionsType"=>"closedCaptionsDisabled", "enableLowLatency"=>false, "projection"=>"rectangular"}}
puts broadcast.inspect
end
task transition_broadcast_live: :environment do |task, args|
google_client = JamRuby::GoogleClient.new
music_session = MusicSession.first
user = User.find_by_email('seth@jamkazam.com')
livestream = music_session.transition_broadcast(google_client, user, music_session.current_broadcast, 'live')
puts livestream.inspect
end
task transition_broadcast_testing: :environment do |task, args|
google_client = JamRuby::GoogleClient.new
music_session = MusicSession.first
user = User.find_by_email('seth@jamkazam.com')
livestream = music_session.transition_broadcast(google_client, user, music_session.current_broadcast, 'testing')
puts livestream.inspect
end
task transition_broadcast_complete: :environment do |task, args|
google_client = JamRuby::GoogleClient.new
music_session = MusicSession.first
user = User.find_by_email('seth@jamkazam.com')
livestream = music_session.transition_broadcast(google_client, user, music_session.current_broadcast, 'complete')
puts livestream.inspect
end
end

View File

@ -7,6 +7,10 @@ class UserManager < BaseManager
@google_client = GoogleClient.new()
end
def get_google_client
@google_client
end
# Note that almost everything can be nil here. This is because when users sign up via social media,
# we don't know much about them.
def signup(options)