* merging VRFS-813 into develop branch; mostly dealing with configureTracks.js and change from client-first on track state (VRFS-852)
This commit is contained in:
commit
95bc077634
|
|
@ -76,3 +76,4 @@ user_progress_tracking.sql
|
|||
whats_next.sql
|
||||
add_user_bio.sql
|
||||
users_geocoding.sql
|
||||
recordings_public_launch.sql
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
-- so that rows can live on after session is over
|
||||
ALTER TABLE recordings DROP CONSTRAINT "recordings_music_session_id_fkey";
|
||||
-- unambiguous declartion that the recording is over or not
|
||||
ALTER TABLE recordings ADD COLUMN is_done BOOLEAN DEFAULT FALSE;
|
||||
|
||||
-- add name and description on claimed_recordings, which is the user's individual view of a recording
|
||||
ALTER TABLE claimed_recordings ADD COLUMN description VARCHAR(8000);
|
||||
ALTER TABLE claimed_recordings ADD COLUMN description_tsv tsvector;
|
||||
ALTER TABLE claimed_recordings ADD COLUMN name_tsv tsvector;
|
||||
|
||||
CREATE TRIGGER tsvectorupdate_description BEFORE INSERT OR UPDATE
|
||||
ON claimed_recordings FOR EACH ROW EXECUTE PROCEDURE
|
||||
tsvector_update_trigger(description_tsv, 'public.jamenglish', description);
|
||||
|
||||
CREATE TRIGGER tsvectorupdate_name BEFORE INSERT OR UPDATE
|
||||
ON claimed_recordings FOR EACH ROW EXECUTE PROCEDURE
|
||||
tsvector_update_trigger(name_tsv, 'public.jamenglish', name);
|
||||
|
||||
CREATE INDEX claimed_recordings_description_tsv_index ON claimed_recordings USING gin(description_tsv);
|
||||
CREATE INDEX claimed_recordings_name_tsv_index ON claimed_recordings USING gin(name_tsv);
|
||||
|
||||
-- copies of connection.client_id and track.id
|
||||
ALTER TABLE recorded_tracks ADD COLUMN client_id VARCHAR(64) NOT NULL;
|
||||
ALTER TABLE recorded_tracks ADD COLUMN track_id VARCHAR(64) NOT NULL;
|
||||
|
||||
-- so that server can correlate to client track
|
||||
ALTER TABLE tracks ADD COLUMN client_track_id VARCHAR(64) NOT NULL;
|
||||
|
|
@ -177,6 +177,7 @@ message MusicianSessionDepart {
|
|||
optional string user_id = 2; // this is the user_id and can be used for user unicast messages
|
||||
optional string username = 3; // meant to be a display name
|
||||
optional string photo_url = 4;
|
||||
optional string recordingId = 5; // if specified, the recording was stopped automatically
|
||||
}
|
||||
|
||||
// route_to: client:
|
||||
|
|
|
|||
|
|
@ -362,6 +362,7 @@ SQL
|
|||
t.instrument = instrument
|
||||
t.connection = connection
|
||||
t.sound = track["sound"]
|
||||
t.client_track_id = track["client_track_id"]
|
||||
t.save
|
||||
connection.tracks << t
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34,4 +34,18 @@ module ValidationMessages
|
|||
EMAIL_ALREADY_TAKEN = "has already been taken"
|
||||
EMAIL_MATCHES_CURRENT = "is same as your current email"
|
||||
INVALID_FPFILE = "is not valid"
|
||||
|
||||
#connection
|
||||
|
||||
SELECT_AT_LEAST_ONE = "Please select at least one track"
|
||||
FAN_CAN_NOT_JOIN_AS_MUSICIAN = "A fan can not join a music session as a musician"
|
||||
MUSIC_SESSION_MUST_BE_SPECIFIED = "A music session must be specified"
|
||||
INVITE_REQUIRED = "You must be invited to join this session"
|
||||
FANS_CAN_NOT_JOIN = "Fans can not join this session"
|
||||
CANT_JOIN_RECORDING_SESSION = "is currently recording"
|
||||
|
||||
# recordings
|
||||
ALREADY_BEING_RECORDED = "already being recorded"
|
||||
NO_LONGER_RECORDING = "no longer recording"
|
||||
NOT_IN_SESSION = "not in session"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
require 'json'
|
||||
require 'resque'
|
||||
|
||||
module JamRuby
|
||||
|
||||
@queue = :audiomixer
|
||||
|
||||
class AudioMixer
|
||||
|
||||
def self.perform(manifest)
|
||||
tmp = Dir::Tmpname.make_tmpname "/var/tmp/audiomixer/manifest-#{manifest['recordingId']}", nil
|
||||
File.open(tmp,"w") do |f|
|
||||
f.write(manifest.to_json)
|
||||
end
|
||||
|
||||
system("tar zxvf some_big_tarball.tar.gz"))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -107,8 +107,8 @@
|
|||
end
|
||||
|
||||
# create a musician left session message
|
||||
def musician_session_depart(session_id, user_id, username, photo_url)
|
||||
left = Jampb::MusicianSessionDepart.new(:session_id => session_id, :user_id => user_id, :username => username, :photo_url => photo_url)
|
||||
def musician_session_depart(session_id, user_id, username, photo_url, recordingId = nil)
|
||||
left = Jampb::MusicianSessionDepart.new(:session_id => session_id, :user_id => user_id, :username => username, :photo_url => photo_url, :recordingId => recordingId)
|
||||
return Jampb::ClientMessage.new(:type => ClientMessage::Type::MUSICIAN_SESSION_DEPART, :route_to => CLIENT_TARGET, :musician_session_depart => left)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ module JamRuby
|
|||
class ClaimedRecording < ActiveRecord::Base
|
||||
|
||||
validates :name, no_profanity: true
|
||||
validates :description, no_profanity: true
|
||||
|
||||
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :claimed_recordings
|
||||
belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :claimed_recordings
|
||||
|
|
@ -16,6 +17,7 @@ module JamRuby
|
|||
end
|
||||
|
||||
self.name = params[:name] unless params[:name].nil?
|
||||
self.description = params[:description] unless params[:description].nil?
|
||||
self.genre = Genre.find(params[:genre]) unless params[:genre].nil?
|
||||
self.is_public = params[:is_public] unless params[:is_public].nil?
|
||||
self.is_downloadable = params[:is_downloadable] unless params[:is_downloadable].nil?
|
||||
|
|
|
|||
|
|
@ -3,11 +3,6 @@ require 'aasm'
|
|||
module JamRuby
|
||||
class Connection < ActiveRecord::Base
|
||||
|
||||
SELECT_AT_LEAST_ONE = "Please select at least one track"
|
||||
FAN_CAN_NOT_JOIN_AS_MUSICIAN = "A fan can not join a music session as a musician"
|
||||
MUSIC_SESSION_MUST_BE_SPECIFIED = "A music session must be specified"
|
||||
INVITE_REQUIRED = "You must be invited to join this session"
|
||||
FANS_CAN_NOT_JOIN = "Fans can not join this session"
|
||||
|
||||
attr_accessor :joining_session
|
||||
|
||||
|
|
@ -72,37 +67,40 @@ module JamRuby
|
|||
def can_join_music_session
|
||||
|
||||
if music_session.nil?
|
||||
errors.add(:music_session, MUSIC_SESSION_MUST_BE_SPECIFIED)
|
||||
errors.add(:music_session, ValidationMessages::MUSIC_SESSION_MUST_BE_SPECIFIED)
|
||||
return false
|
||||
end
|
||||
|
||||
if as_musician
|
||||
unless self.user.musician
|
||||
errors.add(:as_musician, FAN_CAN_NOT_JOIN_AS_MUSICIAN)
|
||||
errors.add(:as_musician, ValidationMessages::FAN_CAN_NOT_JOIN_AS_MUSICIAN)
|
||||
return false
|
||||
end
|
||||
|
||||
if music_session.musician_access
|
||||
if music_session.approval_required
|
||||
unless music_session.creator == user || music_session.invited_musicians.exists?(user)
|
||||
errors.add(:approval_required, INVITE_REQUIRED)
|
||||
errors.add(:approval_required, ValidationMessages::INVITE_REQUIRED)
|
||||
return false
|
||||
end
|
||||
end
|
||||
else
|
||||
unless music_session.creator == user || music_session.invited_musicians.exists?(user)
|
||||
errors.add(:musician_access, INVITE_REQUIRED)
|
||||
errors.add(:musician_access, ValidationMessages::INVITE_REQUIRED)
|
||||
return false
|
||||
end
|
||||
end
|
||||
else
|
||||
unless self.music_session.fan_access
|
||||
# it's someone joining as a fan, and the only way a fan can join is if fan_access is true
|
||||
errors.add(:fan_access, FANS_CAN_NOT_JOIN)
|
||||
errors.add(:fan_access, ValidationMessages::FANS_CAN_NOT_JOIN)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if music_session.is_recording?
|
||||
errors.add(:music_session, ValidationMessages::CANT_JOIN_RECORDING_SESSION)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
|
@ -120,7 +118,7 @@ module JamRuby
|
|||
private
|
||||
def require_at_least_one_track_when_in_session
|
||||
if tracks.count == 0
|
||||
errors.add(:genres, SELECT_AT_LEAST_ONE)
|
||||
errors.add(:genres, ValidationMessages::SELECT_AT_LEAST_ONE)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
module JamRuby
|
||||
class MusicSession < ActiveRecord::Base
|
||||
|
||||
self.primary_key = 'id'
|
||||
|
||||
attr_accessor :legal_terms, :skip_genre_validation
|
||||
|
|
@ -17,8 +16,7 @@ module JamRuby
|
|||
|
||||
has_many :fan_invitations, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::FanInvitation"
|
||||
has_many :invited_fans, :through => :fan_invitations, :class_name => "JamRuby::User", :foreign_key => "receiver_id", :source => :receiver
|
||||
has_one :recording, :class_name => "JamRuby::Recording", :inverse_of => :music_session
|
||||
|
||||
has_many :recordings, :class_name => "JamRuby::Recording", :inverse_of => :music_session
|
||||
belongs_to :band, :inverse_of => :music_sessions, :class_name => "JamRuby::Band", :foreign_key => "band_id"
|
||||
|
||||
after_save :require_at_least_one_genre, :limit_max_genres
|
||||
|
|
@ -38,7 +36,7 @@ module JamRuby
|
|||
|
||||
def creator_is_musician
|
||||
unless creator.musician?
|
||||
errors.add(:creator, "creator must be a musician")
|
||||
errors.add(:creator, "must be a musician")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -168,7 +166,22 @@ module JamRuby
|
|||
def access? user
|
||||
return self.users.exists? user
|
||||
end
|
||||
|
||||
|
||||
# is this music session currently recording?
|
||||
def is_recording?
|
||||
recordings.where(:duration => nil).count > 0
|
||||
end
|
||||
|
||||
def recording
|
||||
recordings.where(:duration => nil).first
|
||||
end
|
||||
|
||||
# stops any active recording
|
||||
def stop_recording
|
||||
current_recording = self.recording
|
||||
current_recording.stop unless current_recording.nil?
|
||||
end
|
||||
|
||||
def to_s
|
||||
return description
|
||||
end
|
||||
|
|
|
|||
|
|
@ -241,10 +241,10 @@ module JamRuby
|
|||
@@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => connection.client_id})
|
||||
end
|
||||
|
||||
def send_musician_session_depart(music_session, client_id, user)
|
||||
def send_musician_session_depart(music_session, client_id, user, recordingId = nil)
|
||||
|
||||
# (1) create notification
|
||||
msg = @@message_factory.musician_session_depart(music_session.id, user.id, user.name, user.photo_url)
|
||||
msg = @@message_factory.musician_session_depart(music_session.id, user.id, user.name, user.photo_url, recordingId)
|
||||
|
||||
# (2) send notification
|
||||
@@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => client_id})
|
||||
|
|
|
|||
|
|
@ -12,17 +12,19 @@ module JamRuby
|
|||
belongs_to :instrument, :class_name => "JamRuby::Instrument"
|
||||
|
||||
validates :sound, :inclusion => {:in => SOUND}
|
||||
|
||||
validates :client_id, :presence => true # not a connection relation on purpose
|
||||
validates :track_id, :presence => true # not a track relation on purpose
|
||||
before_destroy :delete_s3_files
|
||||
|
||||
# Copy an ephemeral track to create a saved one. Some fields are ok with defaults
|
||||
def self.create_from_track(track, recording)
|
||||
recorded_track = self.new
|
||||
recorded_track.recording = recording
|
||||
recorded_track.client_id = track.connection.client_id
|
||||
recorded_track.track_id = track.id
|
||||
recorded_track.user = track.connection.user
|
||||
recorded_track.instrument = track.instrument
|
||||
recorded_track.sound = track.sound
|
||||
recorded_track.save
|
||||
recorded_track
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,51 +3,48 @@ module JamRuby
|
|||
|
||||
self.primary_key = 'id'
|
||||
|
||||
attr_accessible :name, :description, :genre, :is_public, :is_downloadable
|
||||
|
||||
has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording
|
||||
has_many :users, :through => :claimed_recordings, :class_name => "JamRuby::User"
|
||||
has_many :users, :through => :recorded_tracks, :class_name => "JamRuby::User"
|
||||
belongs_to :owner, :class_name => "JamRuby::User", :inverse_of => :owned_recordings
|
||||
belongs_to :band, :class_name => "JamRuby::Band", :inverse_of => :recordings
|
||||
belongs_to :music_session, :class_name => "JamRuby::MusicSession", :inverse_of => :recording
|
||||
belongs_to :music_session, :class_name => "JamRuby::MusicSession", :inverse_of => :recordings
|
||||
has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording
|
||||
has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id
|
||||
|
||||
|
||||
validates :music_session, :presence => true
|
||||
validate :not_already_recording, :on => :create
|
||||
validate :already_stopped_recording
|
||||
|
||||
def not_already_recording
|
||||
if music_session.is_recording?
|
||||
errors.add(:music_session, ValidationMessages::ALREADY_BEING_RECORDED)
|
||||
end
|
||||
end
|
||||
|
||||
def already_stopped_recording
|
||||
if is_done && is_done_was
|
||||
errors.add(:music_session, ValidationMessages::NO_LONGER_RECORDING)
|
||||
end
|
||||
end
|
||||
|
||||
# Start recording a session.
|
||||
def self.start(music_session_id, owner)
|
||||
|
||||
def self.start(music_session, owner)
|
||||
recording = nil
|
||||
|
||||
# Use a transaction and lock to avoid races.
|
||||
ActiveRecord::Base.transaction do
|
||||
music_session = MusicSession.find(music_session_id, :lock => true)
|
||||
|
||||
if music_session.nil?
|
||||
raise PermissionError, "the session has ended"
|
||||
end
|
||||
|
||||
unless music_session.recording.nil?
|
||||
raise PermissionError, "the session is already being recorded"
|
||||
end
|
||||
|
||||
music_session.with_lock do
|
||||
recording = Recording.new
|
||||
recording.music_session = music_session
|
||||
recording.owner = owner
|
||||
|
||||
recording.band = music_session.band
|
||||
|
||||
music_session.connections.each do |connection|
|
||||
# Note that we do NOT connect the recording to any users at this point.
|
||||
# That ONLY happens if a user clicks 'save'
|
||||
# recording.users << connection.user
|
||||
connection.tracks.each do |track|
|
||||
RecordedTrack.create_from_track(track, recording)
|
||||
recording.recorded_tracks << RecordedTrack.create_from_track(track, recording)
|
||||
end
|
||||
end
|
||||
|
||||
# Note that I believe this can be nil.
|
||||
recording.band = music_session.band
|
||||
recording.save
|
||||
|
||||
music_session.recording = recording
|
||||
music_session.save
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -55,10 +52,10 @@ module JamRuby
|
|||
# NEED TO SEND NOTIFICATION TO ALL USERS IN THE SESSION THAT RECORDING HAS STARTED HERE.
|
||||
# I'LL STUB IT A BIT. NOTE THAT I REDO THE FIND HERE BECAUSE I DON'T WANT TO SEND THESE
|
||||
# NOTIFICATIONS WHILE THE DB ROW IS LOCKED
|
||||
music_session = MusicSession.find(music_session_id)
|
||||
music_session.connections.each do |connection|
|
||||
# connection.notify_recording_has_started
|
||||
end
|
||||
#music_session = MusicSession.find(music_session_id)
|
||||
#music_session.connections.each do |connection|
|
||||
# # connection.notify_recording_has_started
|
||||
#end
|
||||
|
||||
recording
|
||||
end
|
||||
|
|
@ -66,34 +63,27 @@ module JamRuby
|
|||
# Stop recording a session
|
||||
def stop
|
||||
# Use a transaction and lock to avoid races.
|
||||
ActiveRecord::Base.transaction do
|
||||
music_session = MusicSession.find(self.music_session_id, :lock => true)
|
||||
if music_session.nil?
|
||||
raise PermissionError, "the session has ended"
|
||||
end
|
||||
unless music_session.recording
|
||||
raise PermissionError, "the session is not currently being recorded"
|
||||
end
|
||||
music_session.recording = nil
|
||||
music_session.save
|
||||
music_session = MusicSession.find_by_id(music_session_id)
|
||||
locker = music_session.nil? ? self : music_session
|
||||
locker.with_lock do
|
||||
self.duration = Time.now - created_at
|
||||
self.is_done = true
|
||||
self.save
|
||||
end
|
||||
|
||||
self.duration = Time.now - created_at
|
||||
save
|
||||
self
|
||||
end
|
||||
|
||||
|
||||
# Called when a user wants to "claim" a recording. To do this, the user must have been one of the tracks in the recording.
|
||||
def claim(user, name, genre, is_public, is_downloadable)
|
||||
if self.users.include?(user)
|
||||
raise PermissionError, "user already claimed this recording"
|
||||
end
|
||||
def claim(user, name, description, genre, is_public, is_downloadable)
|
||||
# if self.users.include?(user)
|
||||
# raise PermissionError, "user already claimed this recording"
|
||||
# end
|
||||
|
||||
unless self.recorded_tracks.find { |recorded_track| recorded_track.user == user }
|
||||
unless self.users.exists?(user)
|
||||
raise PermissionError, "user was not in this session"
|
||||
end
|
||||
|
||||
unless self.music_session.nil?
|
||||
if self.music_session.is_recording?
|
||||
raise PermissionError, "recording cannot be claimed while it is being recorded"
|
||||
end
|
||||
|
||||
|
|
@ -105,11 +95,11 @@ module JamRuby
|
|||
claimed_recording.user = user
|
||||
claimed_recording.recording = self
|
||||
claimed_recording.name = name
|
||||
claimed_recording.description = description
|
||||
claimed_recording.genre = genre
|
||||
claimed_recording.is_public = is_public
|
||||
claimed_recording.is_downloadable = is_downloadable
|
||||
self.claimed_recordings << claimed_recording
|
||||
save
|
||||
|
||||
claimed_recording
|
||||
end
|
||||
|
|
|
|||
|
|
@ -38,7 +38,68 @@ module JamRuby
|
|||
return query
|
||||
end
|
||||
|
||||
def self.save(id, connection_id, instrument_id, sound)
|
||||
|
||||
# this is a bit different from a normal track synchronization in that the client just sends up all tracks,
|
||||
# ... some may already exist
|
||||
def self.sync(clientId, tracks)
|
||||
result = []
|
||||
|
||||
Track.transaction do
|
||||
connection = Connection.find_by_client_id!(clientId)
|
||||
|
||||
if tracks.length == 0
|
||||
connection.tracks.delete_all
|
||||
else
|
||||
connection_tracks = connection.tracks
|
||||
|
||||
# we will prune from this as we find matching tracks
|
||||
to_delete = Set.new(connection_tracks)
|
||||
to_add = Array.new(tracks)
|
||||
|
||||
connection_tracks.each do |connection_track|
|
||||
tracks.each do |track|
|
||||
if track[:id] == connection_track.id || track[:client_track_id] == connection_track.client_track_id;
|
||||
to_delete.delete(connection_track)
|
||||
to_add.delete(track)
|
||||
# don't update connection_id or client_id; it's unknown what would happen if these changed mid-session
|
||||
connection_track.instrument = Instrument.find(track[:instrument_id])
|
||||
connection_track.sound = track[:sound]
|
||||
connection_track.client_track_id = track[:client_track_id]
|
||||
if connection_track.save
|
||||
result.push(connection_track)
|
||||
next
|
||||
else
|
||||
result = connection_track
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
to_add.each do |track|
|
||||
connection_track = Track.new
|
||||
connection_track.connection = connection
|
||||
connection_track.instrument = Instrument.find(track[:instrument_id])
|
||||
connection_track.sound = track[:sound]
|
||||
connection_track.client_track_id = track[:client_track_id]
|
||||
if connection_track.save
|
||||
result.push(connection_track)
|
||||
else
|
||||
result = connection_track
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
to_delete.each do| delete_me |
|
||||
delete_me.delete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def self.save(id, connection_id, instrument_id, sound, client_track_id)
|
||||
if id.nil?
|
||||
track = Track.new()
|
||||
track.connection_id = connection_id
|
||||
|
|
@ -54,6 +115,10 @@ module JamRuby
|
|||
track.sound = sound
|
||||
end
|
||||
|
||||
unless client_track_id.nil?
|
||||
track.client_track_id = client_track_id
|
||||
end
|
||||
|
||||
track.updated_at = Time.now.getutc
|
||||
track.save
|
||||
return track
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ module JamRuby
|
|||
validates :first_name, presence: true, length: {maximum: 50}, no_profanity: true
|
||||
validates :last_name, presence: true, length: {maximum: 50}, no_profanity: true
|
||||
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
|
||||
validates :email, presence: true, format: {with: VALID_EMAIL_REGEX}
|
||||
validates :email, presence: true, format: {with: VALID_EMAIL_REGEX}
|
||||
validates :update_email, presence: true, format: {with: VALID_EMAIL_REGEX}, :if => :updating_email
|
||||
|
||||
validates_length_of :password, minimum: 6, maximum: 100, :if => :should_validate_password?
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ FactoryGirl.define do
|
|||
|
||||
factory :track, :class => JamRuby::Track do
|
||||
sound "mono"
|
||||
|
||||
sequence(:client_track_id) { |n| "client_track_id#{n}"}
|
||||
end
|
||||
|
||||
factory :recorded_track, :class => JamRuby::RecordedTrack do
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ require 'spec_helper'
|
|||
# these tests avoid the use of ActiveRecord and FactoryGirl to do blackbox, non test-instrumented tests
|
||||
describe ConnectionManager do
|
||||
|
||||
TRACKS = [{"instrument_id" => "electric guitar", "sound" => "mono"}]
|
||||
TRACKS = [{"instrument_id" => "electric guitar", "sound" => "mono", "client_track_id" => "some_client_track_id"}]
|
||||
|
||||
before do
|
||||
@conn = PG::Connection.new(:dbname => SpecDb::TEST_DB_NAME, :user => "postgres", :password => "postgres", :host => "localhost")
|
||||
|
|
@ -287,7 +287,7 @@ describe ConnectionManager do
|
|||
|
||||
connection = @connman.join_music_session(user, client_id2, music_session, true, TRACKS)
|
||||
connection.errors.size.should == 1
|
||||
connection.errors.get(:as_musician).should == [Connection::FAN_CAN_NOT_JOIN_AS_MUSICIAN]
|
||||
connection.errors.get(:as_musician).should == [ValidationMessages::FAN_CAN_NOT_JOIN_AS_MUSICIAN]
|
||||
end
|
||||
|
||||
it "as_musician is coerced to boolean" do
|
||||
|
|
@ -352,7 +352,7 @@ describe ConnectionManager do
|
|||
@connman.create_connection(user_id, client_id, "1.1.1.1")
|
||||
connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS)
|
||||
connection.errors.size.should == 1
|
||||
connection.errors.get(:music_session).should == [Connection::MUSIC_SESSION_MUST_BE_SPECIFIED]
|
||||
connection.errors.get(:music_session).should == [ValidationMessages::MUSIC_SESSION_MUST_BE_SPECIFIED]
|
||||
end
|
||||
|
||||
it "join_music_session fails if approval_required and no invitation, but generates join_request" do
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ describe Mix do
|
|||
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
|
||||
@music_session.connections << @connection
|
||||
@music_session.save
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@mix = Mix.schedule(@recording, "{}")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -394,5 +394,44 @@ describe MusicSession do
|
|||
music_session.valid?.should be_false
|
||||
end
|
||||
|
||||
it "is_recording? returns false if not recording" do
|
||||
user1 = FactoryGirl.create(:user)
|
||||
music_session = FactoryGirl.build(:music_session, :creator => user1)
|
||||
music_session.is_recording?.should be_false
|
||||
end
|
||||
|
||||
describe "recordings" do
|
||||
|
||||
before(:each) do
|
||||
@user1 = FactoryGirl.create(:user)
|
||||
@connection = FactoryGirl.create(:connection, :user => @user1)
|
||||
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
|
||||
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
|
||||
@music_session = FactoryGirl.create(:music_session, :creator => @user1, :musician_access => true)
|
||||
@music_session.connections << @connection
|
||||
@music_session.save
|
||||
end
|
||||
|
||||
describe "not recording" do
|
||||
it "stop_recording should return nil if not recording" do
|
||||
@music_session.stop_recording.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "currently recording" do
|
||||
before(:each) do
|
||||
@recording = FactoryGirl.create(:recording, :music_session => @music_session, :owner => @user1)
|
||||
end
|
||||
|
||||
it "is_recording? returns true if recording" do
|
||||
@music_session.is_recording?.should be_true
|
||||
end
|
||||
|
||||
it "stop_recording should return recording object if recording" do
|
||||
@music_session.stop_recording.should == @recording
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -79,11 +79,11 @@ describe User do
|
|||
music_session = FactoryGirl.create(:music_session, :creator => uu, :musician_access => true)
|
||||
music_session.connections << connection
|
||||
music_session.save
|
||||
recording = Recording.start(music_session.id, uu)
|
||||
recording = Recording.start(music_session, uu)
|
||||
recording.stop
|
||||
recording.reload
|
||||
genre = FactoryGirl.create(:genre)
|
||||
recording.claim(uu, "name", genre, true, true)
|
||||
recording.claim(uu, "name", "description", genre, true, true)
|
||||
recording.reload
|
||||
recording
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ describe RecordedTrack do
|
|||
@user = FactoryGirl.create(:user)
|
||||
@connection = FactoryGirl.create(:connection, :user => @user)
|
||||
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
|
||||
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
|
||||
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
|
||||
@recording = FactoryGirl.create(:recording, :owner => @user)
|
||||
@recording = FactoryGirl.create(:recording, :music_session => @music_session, :owner => @user)
|
||||
end
|
||||
|
||||
it "should copy from a regular track properly" do
|
||||
|
|
@ -17,6 +18,8 @@ describe RecordedTrack do
|
|||
@recorded_track.instrument.id.should == @track.instrument.id
|
||||
@recorded_track.next_part_to_upload.should == 0
|
||||
@recorded_track.fully_uploaded.should == false
|
||||
@recorded_track.client_id = @connection.client_id
|
||||
@recorded_track.track_id = @track.id
|
||||
end
|
||||
|
||||
it "should update the next part to upload properly" do
|
||||
|
|
@ -38,11 +41,13 @@ describe RecordedTrack do
|
|||
|
||||
it "properly finds a recorded track given its upload filename" do
|
||||
@recorded_track = RecordedTrack.create_from_track(@track, @recording)
|
||||
@recorded_track.save.should be_true
|
||||
RecordedTrack.find_by_upload_filename("recording_#{@recorded_track.id}").should == @recorded_track
|
||||
end
|
||||
|
||||
it "gets a url for the track" do
|
||||
@recorded_track = RecordedTrack.create_from_track(@track, @recording)
|
||||
@recorded_track.save.should be_true
|
||||
@recorded_track.url.should == S3Manager.url(S3Manager.hashed_filename("recorded_track", @recorded_track.id))
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,23 +5,17 @@ describe Recording do
|
|||
before do
|
||||
S3Manager.set_unit_test
|
||||
@user = FactoryGirl.create(:user)
|
||||
@connection = FactoryGirl.create(:connection, :user => @user)
|
||||
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
|
||||
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
|
||||
@connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session)
|
||||
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
|
||||
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
|
||||
@music_session.connections << @connection
|
||||
@music_session.save
|
||||
end
|
||||
|
||||
it "should not start a recording if the music session doesnt exist" do
|
||||
expect { Recording.start("bad_music_session_id", @user) }.to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
it "should set up the recording properly when recording is started with 1 user in the session" do
|
||||
@music_session.recording.should == nil
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@music_session.is_recording?.should be_false
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@music_session.reload
|
||||
@music_session.recording.should == @recording
|
||||
@music_session.recordings[0].should == @recording
|
||||
@recording.owner_id.should == @user.id
|
||||
|
||||
@recorded_tracks = RecordedTrack.where(:recording_id => @recording.id)
|
||||
|
|
@ -31,31 +25,34 @@ describe Recording do
|
|||
end
|
||||
|
||||
it "should not start a recording if the session is already being recorded" do
|
||||
Recording.start(@music_session.id, @user)
|
||||
expect { Recording.start(@music_session.id, @user) }.to raise_error
|
||||
Recording.start(@music_session, @user).errors.any?.should be_false
|
||||
recording = Recording.start(@music_session, @user)
|
||||
|
||||
recording.valid?.should_not be_true
|
||||
recording.errors[:music_session].should_not be_nil
|
||||
end
|
||||
|
||||
it "should return the state to normal properly when you stop a recording" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@music_session.reload
|
||||
@music_session.recording.should == nil
|
||||
@recording.reload
|
||||
@recording.music_session.should == nil
|
||||
@music_session.is_recording?.should be_false
|
||||
end
|
||||
|
||||
|
||||
it "should error when you stop a recording twice" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
expect { @recording.stop }.to raise_error
|
||||
@recording.errors.any?.should be_false
|
||||
@recording.stop
|
||||
@recording.errors.any?.should be_true
|
||||
end
|
||||
|
||||
it "should be able to start, stop then start a recording again for the same music session" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording2 = Recording.start(@music_session.id, @user)
|
||||
@music_session.recording.should == @recording2
|
||||
@recording2 = Recording.start(@music_session, @user)
|
||||
@music_session.recordings.exists?(@recording2).should be_true
|
||||
end
|
||||
|
||||
it "should NOT attach the recording to all users in a the music session when recording started" do
|
||||
|
|
@ -66,7 +63,7 @@ describe Recording do
|
|||
|
||||
@music_session.connections << @connection2
|
||||
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@user.recordings.length.should == 0
|
||||
#@user.recordings.first.should == @recording
|
||||
|
||||
|
|
@ -75,7 +72,7 @@ describe Recording do
|
|||
end
|
||||
|
||||
it "should report correctly whether its tracks have been uploaded" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.uploaded?.should == false
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
|
|
@ -85,7 +82,7 @@ describe Recording do
|
|||
end
|
||||
|
||||
it "should destroy a recording and all its recorded tracks properly" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
@recorded_track = @recording.recorded_tracks.first
|
||||
|
|
@ -95,11 +92,11 @@ describe Recording do
|
|||
end
|
||||
|
||||
it "should allow a user to claim a recording" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
@genre = FactoryGirl.create(:genre)
|
||||
@recording.claim(@user, "name", @genre, true, true)
|
||||
@recording.claim(@user, "name", "description", @genre, true, true)
|
||||
@recording.reload
|
||||
@recording.users.length.should == 1
|
||||
@recording.users.first.should == @user
|
||||
|
|
@ -108,56 +105,58 @@ describe Recording do
|
|||
@recording.claimed_recordings.length.should == 1
|
||||
@claimed_recording = @recording.claimed_recordings.first
|
||||
@claimed_recording.name.should == "name"
|
||||
@claimed_recording.description.should == "description"
|
||||
@claimed_recording.genre.should == @genre
|
||||
@claimed_recording.is_public.should == true
|
||||
@claimed_recording.is_downloadable.should == true
|
||||
end
|
||||
|
||||
it "should fail if a user who was not in the session claims a recording" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
user2 = FactoryGirl.create(:user)
|
||||
expect { @recording.claim(user2) }.to raise_error
|
||||
expect { @recording.claim(user2, "name", "description", @genre, true, true) }.to raise_error
|
||||
end
|
||||
|
||||
it "should fail if a user tries to claim a recording twice" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
@genre = FactoryGirl.create(:genre)
|
||||
@recording.claim(@user, "name", @genre, true, true)
|
||||
@recording.claim(@user, "name", "description", @genre, true, true)
|
||||
@recording.reload
|
||||
expect { @recording.claim(@user, "name", @genre, true, true) }.to raise_error
|
||||
expect { @recording.claim(@user, "name", "description", @genre, true, true) }.to raise_error
|
||||
end
|
||||
|
||||
it "should allow editing metadata for claimed recordings" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
@genre = FactoryGirl.create(:genre)
|
||||
@claimed_recording = @recording.claim(@user, "name", @genre, true, true)
|
||||
@claimed_recording = @recording.claim(@user, "name", "description", @genre, true, true)
|
||||
@genre2 = FactoryGirl.create(:genre)
|
||||
@claimed_recording.update_fields(@user, :name => "name2", :genre => @genre2.id, :is_public => false, :is_downloadable => false)
|
||||
@claimed_recording.update_fields(@user, :name => "name2", :description => "description2", :genre => @genre2.id, :is_public => false, :is_downloadable => false)
|
||||
@claimed_recording.reload
|
||||
@claimed_recording.name.should == "name2"
|
||||
@claimed_recording.description.should == "description2"
|
||||
@claimed_recording.genre.should == @genre2
|
||||
@claimed_recording.is_public.should == false
|
||||
@claimed_recording.is_downloadable.should == false
|
||||
end
|
||||
|
||||
it "should only allow the owner to edit a claimed recording" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
@genre = FactoryGirl.create(:genre)
|
||||
@claimed_recording = @recording.claim(@user, "name", @genre, true, true)
|
||||
@claimed_recording = @recording.claim(@user, "name", "description", @genre, true, true)
|
||||
@user2 = FactoryGirl.create(:user)
|
||||
expect { @claimed_recording.update_fields(@user2, "name2") }.to raise_error
|
||||
end
|
||||
|
||||
it "should record the duration of the recording properly" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.duration.should be_nil
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
|
|
@ -173,35 +172,35 @@ describe Recording do
|
|||
@track = FactoryGirl.create(:track, :connection => @connection2, :instrument => @instrument)
|
||||
@music_session.connections << @connection2
|
||||
@music_session.save
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
@genre = FactoryGirl.create(:genre)
|
||||
@claimed_recording = @recording.claim(@user, "name", @genre, true, true)
|
||||
@claimed_recording = @recording.claim(@user, "name", "description", @genre, true, true)
|
||||
expect { @claimed_recordign.discard(@user2) }.to raise_error
|
||||
@claimed_recording = @recording.claim(@user2, "name2", @genre, true, true)
|
||||
@claimed_recording = @recording.claim(@user2, "name2", "description2", @genre, true, true)
|
||||
@claimed_recording.discard(@user2)
|
||||
@recording.reload
|
||||
@recording.claimed_recordings.length.should == 1
|
||||
end
|
||||
|
||||
it "should destroy the entire recording if there was only one claimed_recording which is discarded" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
@genre = FactoryGirl.create(:genre)
|
||||
@claimed_recording = @recording.claim(@user, "name", @genre, true, true)
|
||||
@claimed_recording = @recording.claim(@user, "name", "description", @genre, true, true)
|
||||
@claimed_recording.discard(@user)
|
||||
expect { Recording.find(@recording.id) }.to raise_error
|
||||
expect { ClaimedRecording.find(@claimed_recording.id) }.to raise_error
|
||||
end
|
||||
|
||||
it "should return a file list for a user properly" do
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
@genre = FactoryGirl.create(:genre)
|
||||
@recording.claim(@user, "Recording", @genre, true, true)
|
||||
@recording.claim(@user, "Recording", "Recording Description", @genre, true, true)
|
||||
Recording.list(@user)["downloads"].length.should == 0
|
||||
Recording.list(@user)["uploads"].length.should == 1
|
||||
file = Recording.list(@user)["uploads"].first
|
||||
|
|
@ -241,7 +240,7 @@ describe Recording do
|
|||
@track2 = FactoryGirl.create(:track, :connection => @connection2, :instrument => @instrument2)
|
||||
@music_session.connections << @connection2
|
||||
@music_session.save
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
#sleep 4
|
||||
@recording.stop
|
||||
@recording.recorded_tracks.length.should == 2
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Track do
|
||||
|
||||
let (:connection) { FactoryGirl.create(:connection) }
|
||||
let (:track) { FactoryGirl.create(:track, :connection => connection)}
|
||||
let (:track2) { FactoryGirl.create(:track, :connection => connection)}
|
||||
|
||||
let (:track_hash) { {:client_track_id => 'client_guid', :sound => 'stereo', :instrument_id => 'drums'} }
|
||||
|
||||
before(:each) do
|
||||
|
||||
end
|
||||
|
||||
describe "sync" do
|
||||
it "create one track" do
|
||||
tracks = Track.sync(connection.client_id, [track_hash])
|
||||
tracks.length.should == 1
|
||||
track = tracks[0]
|
||||
track.client_track_id.should == track_hash[:client_track_id]
|
||||
track.sound = track_hash[:sound]
|
||||
track.instrument.should == Instrument.find('drums')
|
||||
end
|
||||
|
||||
it "create two tracks" do
|
||||
tracks = Track.sync(connection.client_id, [track_hash, track_hash])
|
||||
tracks.length.should == 2
|
||||
track = tracks[0]
|
||||
track.client_track_id.should == track_hash[:client_track_id]
|
||||
track.sound = track_hash[:sound]
|
||||
track.instrument.should == Instrument.find('drums')
|
||||
track = tracks[1]
|
||||
track.client_track_id.should == track_hash[:client_track_id]
|
||||
track.sound = track_hash[:sound]
|
||||
track.instrument.should == Instrument.find('drums')
|
||||
end
|
||||
|
||||
it "delete only track" do
|
||||
track.id.should_not be_nil
|
||||
connection.tracks.length.should == 1
|
||||
tracks = Track.sync(connection.client_id, [])
|
||||
tracks.length.should == 0
|
||||
end
|
||||
|
||||
it "delete one of two tracks using .id to correlate" do
|
||||
|
||||
track.id.should_not be_nil
|
||||
track2.id.should_not be_nil
|
||||
connection.tracks.length.should == 2
|
||||
tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => 'client_guid_new', :sound => 'mono', :instrument_id => 'drums'}])
|
||||
tracks.length.should == 1
|
||||
found = tracks[0]
|
||||
found.id.should == track.id
|
||||
found.sound.should == 'mono'
|
||||
found.client_track_id.should == 'client_guid_new'
|
||||
end
|
||||
|
||||
it "delete one of two tracks using .client_track_id to correlate" do
|
||||
|
||||
track.id.should_not be_nil
|
||||
track2.id.should_not be_nil
|
||||
connection.tracks.length.should == 2
|
||||
tracks = Track.sync(connection.client_id, [{:client_track_id => track.client_track_id, :sound => 'mono', :instrument_id => 'drums'}])
|
||||
tracks.length.should == 1
|
||||
found = tracks[0]
|
||||
found.id.should == track.id
|
||||
found.sound.should == 'mono'
|
||||
found.client_track_id.should == track.client_track_id
|
||||
end
|
||||
|
||||
|
||||
it "updates a single track using .id to correlate" do
|
||||
track.id.should_not be_nil
|
||||
connection.tracks.length.should == 1
|
||||
tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => 'client_guid_new', :sound => 'mono', :instrument_id => 'drums'}])
|
||||
tracks.length.should == 1
|
||||
found = tracks[0]
|
||||
found.id.should == track.id
|
||||
found.sound.should == 'mono'
|
||||
found.client_track_id.should == 'client_guid_new'
|
||||
end
|
||||
|
||||
it "updates a single track using .client_track_id to correlate" do
|
||||
track.id.should_not be_nil
|
||||
connection.tracks.length.should == 1
|
||||
tracks = Track.sync(connection.client_id, [{:client_track_id => track.client_track_id, :sound => 'mono', :instrument_id => 'drums'}])
|
||||
tracks.length.should == 1
|
||||
found = tracks[0]
|
||||
found.id.should == track.id
|
||||
found.sound.should == 'mono'
|
||||
found.client_track_id.should == track.client_track_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -104,7 +104,9 @@
|
|||
payload = message[messageType],
|
||||
callbacks = server.dispatchTable[message.type];
|
||||
|
||||
logger.log("server.onMessage:" + messageType + " payload:" + JSON.stringify(payload));
|
||||
if(message.type != context.JK.MessageType.HEARTBEAT_ACK) {
|
||||
logger.log("server.onMessage:" + messageType + " payload:" + JSON.stringify(payload));
|
||||
}
|
||||
|
||||
if (callbacks !== undefined) {
|
||||
var len = callbacks.length;
|
||||
|
|
@ -113,6 +115,7 @@
|
|||
callbacks[i](message, payload);
|
||||
} catch (ex) {
|
||||
logger.warn('exception in callback for websocket message:' + ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -131,7 +134,9 @@
|
|||
|
||||
var jsMessage = JSON.stringify(message);
|
||||
|
||||
logger.log("server.send(" + jsMessage + ")");
|
||||
if(message.type != context.JK.MessageType.HEARTBEAT) {
|
||||
logger.log("server.send(" + jsMessage + ")");
|
||||
}
|
||||
if (server !== undefined && server.socket !== undefined && server.socket.send !== undefined) {
|
||||
server.socket.send(jsMessage);
|
||||
} else {
|
||||
|
|
@ -152,6 +157,11 @@
|
|||
server.send(loginMessage);
|
||||
};
|
||||
|
||||
/** with the advent of the reliable UDP channel, this is no longer how messages are sent from client-to-clent
|
||||
* however, the mechanism still exists and is useful in test contexts; and maybe in the future
|
||||
* @param receiver_id client ID of message to send
|
||||
* @param message the actual message
|
||||
*/
|
||||
server.sendP2PMessage = function(receiver_id, message) {
|
||||
logger.log("P2P message from [" + server.clientID + "] to [" + receiver_id + "]: " + message);
|
||||
var outgoing_msg = msg_factory.client_p2p_message(server.clientID, receiver_id, message);
|
||||
|
|
@ -192,4 +202,5 @@
|
|||
}
|
||||
|
||||
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -71,7 +71,6 @@
|
|||
|
||||
// set arrays
|
||||
inputUnassignedList = _loadList(ASSIGNMENT.UNASSIGNED, true, false);
|
||||
console.log("inputUnassignedList: " + JSON.stringify(inputUnassignedList));
|
||||
track2AudioInputChannels = _loadList(ASSIGNMENT.TRACK2, true, false);
|
||||
}
|
||||
|
||||
|
|
@ -125,18 +124,25 @@
|
|||
}
|
||||
|
||||
function saveSettings() {
|
||||
if (!context.JK.verifyNotRecordingForTrackChange(app)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateSettings()) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveTrack();
|
||||
|
||||
app.layout.closeDialog('add-track');
|
||||
}
|
||||
|
||||
function saveTrack() {
|
||||
// TRACK 2 INPUTS
|
||||
var trackId = null;
|
||||
$("#add-track2-input > option").each(function() {
|
||||
logger.debug("Saving track 2 input = " + this.value);
|
||||
trackId = this.value;
|
||||
context.jamClient.TrackSetAssignment(this.value, true, ASSIGNMENT.TRACK2);
|
||||
});
|
||||
|
||||
|
|
@ -150,12 +156,52 @@
|
|||
// UPDATE SERVER
|
||||
logger.debug("Adding track with instrument " + instrumentText);
|
||||
var data = {};
|
||||
// use the first track's connection_id (not sure why we need this on the track data model)
|
||||
logger.debug("myTracks[0].connection_id=" + myTracks[0].connection_id);
|
||||
data.connection_id = myTracks[0].connection_id;
|
||||
data.instrument_id = instrumentText;
|
||||
data.sound = "stereo";
|
||||
sessionModel.addTrack(sessionId, data);
|
||||
|
||||
context.jamClient.TrackSaveAssignments();
|
||||
|
||||
/**
|
||||
setTimeout(function() {
|
||||
var inputTracks = context.JK.TrackHelpers.getTracks(context.jamClient, 2);
|
||||
|
||||
// this is some ugly logic coming up, here's why:
|
||||
// we need the id (guid) that the backend generated for the new track we just added
|
||||
// to get it, we need to make sure 2 tracks come back, and then grab the track that
|
||||
// is not the one we just added.
|
||||
if(inputTracks.length != 2) {
|
||||
var msg = "because we just added a track, there should be 2 available, but we found: " + inputTracks.length;
|
||||
logger.error(msg);
|
||||
alert(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
var client_track_id = null;
|
||||
$.each(inputTracks, function(index, track) {
|
||||
|
||||
|
||||
console.log("track: %o, myTrack: %o", track, myTracks[0]);
|
||||
if(track.id != myTracks[0].id) {
|
||||
client_track_id = track.id;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if(client_track_id == null)
|
||||
{
|
||||
var msg = "unable to find matching backend track for id: " + this.value;
|
||||
logger.error(msg);
|
||||
alert(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
// use the first track's connection_id (not sure why we need this on the track data model)
|
||||
data.connection_id = myTracks[0].connection_id;
|
||||
data.instrument_id = instrumentText;
|
||||
data.sound = "stereo";
|
||||
data.client_track_id = client_track_id;
|
||||
sessionModel.addTrack(sessionId, data);
|
||||
}, 1000);
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
function validateSettings() {
|
||||
|
|
|
|||
|
|
@ -18,4 +18,5 @@
|
|||
//= require jquery.Jcrop
|
||||
//= require jquery.naturalsize
|
||||
//= require jquery.queryparams
|
||||
//= require globals
|
||||
//= require_directory .
|
||||
|
|
|
|||
|
|
@ -213,7 +213,6 @@
|
|||
|
||||
// remove option 1 from voice chat type dropdown if no music (based on what's unused on the Music Audio tab) or chat inputs are available
|
||||
if ($('#audio-inputs-unused > option').size() === 0 && chatOtherUnassignedList.length === 0 && chatOtherAssignedList.length === 0) {
|
||||
logger.debug("Removing Option 1 from Voice Chat dropdown.");
|
||||
$option1.remove();
|
||||
}
|
||||
else {
|
||||
|
|
@ -353,6 +352,7 @@
|
|||
|
||||
// load Audio Driver dropdown
|
||||
devices = context.jamClient.TrackGetDevices();
|
||||
logger.debug("Called TrackGetDevices with response " + JSON.stringify(devices));
|
||||
var keys = Object.keys(devices);
|
||||
|
||||
for (var i=0; i < keys.length; i++) {
|
||||
|
|
@ -471,13 +471,13 @@
|
|||
|
||||
function _initMusicTabData() {
|
||||
inputUnassignedList = _loadList(ASSIGNMENT.UNASSIGNED, true, false);
|
||||
logger.debug("inputUnassignedList=" + JSON.stringify(inputUnassignedList));
|
||||
//logger.debug("inputUnassignedList=" + JSON.stringify(inputUnassignedList));
|
||||
|
||||
track1AudioInputChannels = _loadList(ASSIGNMENT.TRACK1, true, false);
|
||||
logger.debug("track1AudioInputChannels=" + JSON.stringify(track1AudioInputChannels));
|
||||
//logger.debug("track1AudioInputChannels=" + JSON.stringify(track1AudioInputChannels));
|
||||
|
||||
track2AudioInputChannels = _loadList(ASSIGNMENT.TRACK2, true, false);
|
||||
logger.debug("track2AudioInputChannels=" + JSON.stringify(track2AudioInputChannels));
|
||||
//logger.debug("track2AudioInputChannels=" + JSON.stringify(track2AudioInputChannels));
|
||||
|
||||
outputUnassignedList = _loadList(ASSIGNMENT.UNASSIGNED, false, false);
|
||||
outputAssignedList = _loadList(ASSIGNMENT.OUTPUT, false, false);
|
||||
|
|
@ -485,16 +485,16 @@
|
|||
|
||||
function _initVoiceChatTabData() {
|
||||
chatUnassignedList = _loadList(ASSIGNMENT.UNASSIGNED, true, false);
|
||||
logger.debug("chatUnassignedList=" + JSON.stringify(chatUnassignedList));
|
||||
//logger.debug("chatUnassignedList=" + JSON.stringify(chatUnassignedList));
|
||||
|
||||
chatAssignedList = _loadList(ASSIGNMENT.CHAT, true, false);
|
||||
logger.debug("chatAssignedList=" + JSON.stringify(chatAssignedList));
|
||||
//logger.debug("chatAssignedList=" + JSON.stringify(chatAssignedList));
|
||||
|
||||
chatOtherUnassignedList = _loadList(ASSIGNMENT.UNASSIGNED, true, true);
|
||||
logger.debug("chatOtherUnassignedList=" + JSON.stringify(chatOtherUnassignedList));
|
||||
//logger.debug("chatOtherUnassignedList=" + JSON.stringify(chatOtherUnassignedList));
|
||||
|
||||
chatOtherAssignedList = _loadList(ASSIGNMENT.CHAT, true, true);
|
||||
logger.debug("chatOtherAssignedList=" + JSON.stringify(chatOtherAssignedList));
|
||||
//logger.debug("chatOtherAssignedList=" + JSON.stringify(chatOtherAssignedList));
|
||||
}
|
||||
|
||||
// TODO: copied in addTrack.js - refactor to common place
|
||||
|
|
@ -548,6 +548,10 @@
|
|||
}
|
||||
|
||||
function saveSettings() {
|
||||
if (!context.JK.verifyNotRecordingForTrackChange(app)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateAudioSettings(false)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -563,9 +567,6 @@
|
|||
|
||||
originalDeviceId = $('#audio-drivers').val();
|
||||
app.layout.closeDialog('configure-audio');
|
||||
|
||||
// refresh Session screen
|
||||
sessionModel.refreshCurrentSession();
|
||||
}
|
||||
|
||||
function saveAudioSettings() {
|
||||
|
|
@ -591,11 +592,6 @@
|
|||
// logger.debug("Saving track 1 instrument = " + instrumentVal);
|
||||
context.jamClient.TrackSetInstrument(ASSIGNMENT.TRACK1, instrumentVal);
|
||||
|
||||
// UPDATE SERVER
|
||||
logger.debug("Updating track " + myTracks[0].trackId + " with instrument " + instrumentText);
|
||||
var data = {};
|
||||
data.instrument_id = instrumentText;
|
||||
sessionModel.updateTrack(sessionId, myTracks[0].trackId, data);
|
||||
|
||||
// TRACK 2 INPUTS
|
||||
var track2Selected = false;
|
||||
|
|
@ -609,25 +605,6 @@
|
|||
// TRACK 2 INSTRUMENT
|
||||
instrumentVal = $('#track2-instrument').val();
|
||||
instrumentText = $('#track2-instrument > option:selected').text().toLowerCase();
|
||||
|
||||
// track 2 new - add
|
||||
if (myTrackCount === 1) {
|
||||
data = {};
|
||||
// use the first track's connection_id (not sure why we need this on the track data model)
|
||||
logger.debug("myTracks[0].connection_id=" + myTracks[0].connection_id);
|
||||
data.connection_id = myTracks[0].connection_id;
|
||||
data.instrument_id = instrumentText;
|
||||
data.sound = "stereo";
|
||||
sessionModel.addTrack(sessionId, data);
|
||||
}
|
||||
// track 2 exists - update
|
||||
else if (myTrackCount === 2) {
|
||||
// UPDATE SERVER
|
||||
logger.debug("Updating track " + myTracks[1].trackId + " with instrument " + instrumentText);
|
||||
data = {};
|
||||
data.instrument_id = instrumentText;
|
||||
sessionModel.updateTrack(sessionId, myTracks[1].trackId, data);
|
||||
}
|
||||
|
||||
logger.debug("Saving track 2 instrument = " + instrumentVal);
|
||||
context.jamClient.TrackSetInstrument(ASSIGNMENT.TRACK2, instrumentVal);
|
||||
|
|
@ -636,7 +613,8 @@
|
|||
// track 2 was removed
|
||||
if (myTrackCount === 2) {
|
||||
logger.debug("Deleting track " + myTracks[1].trackId);
|
||||
sessionModel.deleteTrack(sessionId, myTracks[1].trackId);
|
||||
client.TrackSetCount(1);
|
||||
//sessionModel.deleteTrack(sessionId, myTracks[1].trackId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -651,6 +629,8 @@
|
|||
logger.debug("Saving session audio output = " + this.value);
|
||||
context.jamClient.TrackSetAssignment(this.value, false, ASSIGNMENT.OUTPUT);
|
||||
});
|
||||
|
||||
context.jamClient.TrackSaveAssignments();
|
||||
}
|
||||
|
||||
function saveVoiceChatSettings() {
|
||||
|
|
@ -799,6 +779,14 @@
|
|||
}
|
||||
|
||||
function _init() {
|
||||
var dialogBindings = {
|
||||
'beforeShow' : function() {
|
||||
return context.JK.verifyNotRecordingForTrackChange(app);
|
||||
}
|
||||
};
|
||||
app.bindDialog('configure-audio', dialogBindings);
|
||||
|
||||
|
||||
// load instrument array for populating listboxes, using client_id in instrument_map as ID
|
||||
context.JK.listInstruments(app, function(instruments) {
|
||||
$.each(instruments, function(index, val) {
|
||||
|
|
@ -807,7 +795,6 @@
|
|||
});
|
||||
|
||||
originalVoiceChat = context.jamClient.TrackGetChatEnable() ? VOICE_CHAT.CHAT : VOICE_CHAT.NO_CHAT;
|
||||
logger.debug("originalVoiceChat=" + originalVoiceChat);
|
||||
|
||||
$('#voice-chat-type').val(originalVoiceChat);
|
||||
|
||||
|
|
@ -820,7 +807,6 @@
|
|||
|
||||
// remove option 1 from voice chat if none are available and not already assigned
|
||||
if (inputUnassignedList.length === 0 && chatAssignedList.length === 0 && chatOtherAssignedList.length === 0 && chatOtherUnassignedList.length === 0) {
|
||||
logger.debug("Removing Option 1 from Voice Chat dropdown.");
|
||||
$option1.remove();
|
||||
}
|
||||
// add it if it doesn't exist
|
||||
|
|
@ -836,7 +822,6 @@
|
|||
events();
|
||||
_init();
|
||||
myTrackCount = myTracks.length;
|
||||
logger.debug("initialize:myTrackCount=" + myTrackCount);
|
||||
};
|
||||
|
||||
this.showMusicAudioPanel = showMusicAudioPanel;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.FakeJamClient = function(app) {
|
||||
context.JK.FakeJamClient = function(app, p2pMessageFactory) {
|
||||
var logger = context.JK.logger;
|
||||
logger.info("*** Fake JamClient instance initialized. ***");
|
||||
|
||||
|
|
@ -18,6 +18,8 @@
|
|||
var device_id = -1;
|
||||
var latencyCallback = null;
|
||||
var frameSize = 2.5;
|
||||
var fakeJamClientRecordings = null;
|
||||
var p2pCallbacks = null;
|
||||
|
||||
function dbg(msg) { logger.debug('FakeJamClient: ' + msg); }
|
||||
|
||||
|
|
@ -142,7 +144,31 @@
|
|||
|
||||
function LatencyUpdated(map) { dbg('LatencyUpdated:' + JSON.stringify(map)); }
|
||||
function LeaveSession(map) { dbg('LeaveSession:' + JSON.stringify(map)); }
|
||||
function P2PMessageReceived(s1,s2) { dbg('P2PMessageReceived:' + s1 + ',' + s2); }
|
||||
|
||||
// this is not a real bridge method; purely used by the fake jam client
|
||||
function RegisterP2PMessageCallbacks(callbacks) {
|
||||
p2pCallbacks = callbacks;
|
||||
}
|
||||
|
||||
function P2PMessageReceived(from, payload) {
|
||||
|
||||
dbg('P2PMessageReceived');
|
||||
// this function is different in that the payload is a JSON ready string;
|
||||
// whereas a real p2p message is base64 encoded binary packaged data
|
||||
|
||||
try {
|
||||
payload = JSON.parse(payload);
|
||||
}
|
||||
catch(e) {
|
||||
logger.warn("unable to parse payload as JSON from client %o, %o, %o", from, e, payload);
|
||||
}
|
||||
|
||||
var callback = p2pCallbacks[payload.type];
|
||||
if(callback) {
|
||||
callback(from, payload);
|
||||
}
|
||||
}
|
||||
|
||||
function JoinSession(sessionId) {dbg('JoinSession:' + sessionId);}
|
||||
function ParticipantLeft(session, participant) {
|
||||
dbg('ParticipantLeft:' + JSON.stringify(session) + ',' +
|
||||
|
|
@ -167,9 +193,21 @@
|
|||
}
|
||||
function StartPlayTest(s) { dbg('StartPlayTest' + JSON.stringify(arguments)); }
|
||||
function StartRecordTest(s) { dbg('StartRecordTest' + JSON.stringify(arguments)); }
|
||||
function StartRecording(map) { dbg('StartRecording' + JSON.stringify(arguments)); }
|
||||
function StartRecording(recordingId, groupedClientTracks) {
|
||||
dbg('StartRecording');
|
||||
fakeJamClientRecordings.StartRecording(recordingId, groupedClientTracks);
|
||||
}
|
||||
function StopPlayTest() { dbg('StopPlayTest'); }
|
||||
function StopRecording(map) { dbg('StopRecording' + JSON.stringify(arguments)); }
|
||||
function StopRecording(recordingId, groupedTracks, errorReason, detail) {
|
||||
dbg('StopRecording');
|
||||
fakeJamClientRecordings.StopRecording(recordingId, groupedTracks, errorReason, detail);
|
||||
}
|
||||
|
||||
function AbortRecording(recordingId, errorReason, errorDetail) {
|
||||
dbg('AbortRecording');
|
||||
fakeJamClientRecordings.AbortRecording(recordingId, errorReason, errorDetail);
|
||||
}
|
||||
|
||||
function TestASIOLatency(s) { dbg('TestASIOLatency' + JSON.stringify(arguments)); }
|
||||
|
||||
function TestLatency(clientID, callbackFunctionName, timeoutCallbackName) {
|
||||
|
|
@ -244,6 +282,11 @@
|
|||
"User@208.191.152.98_*"
|
||||
];
|
||||
}
|
||||
|
||||
function RegisterRecordingCallbacks(startRecordingCallbackName, stopRecordingCallbackName, startedRecordingCallbackName, stoppedRecordingCallbackName, abortedRecordingCallbackName) {
|
||||
fakeJamClientRecordings.RegisterRecordingCallbacks(startRecordingCallbackName, stopRecordingCallbackName, startedRecordingCallbackName,stoppedRecordingCallbackName, abortedRecordingCallbackName);
|
||||
}
|
||||
|
||||
function SessionRegisterCallback(callbackName) {
|
||||
eventCallbackName = callbackName;
|
||||
if (callbackTimer) { context.clearInterval(callbackTimer); }
|
||||
|
|
@ -483,8 +526,18 @@
|
|||
}
|
||||
function ClientUpdateStartUpdate(path, successCallback, failureCallback) {}
|
||||
|
||||
// -------------------------------
|
||||
// fake jam client methods
|
||||
// not a part of the actual bridge
|
||||
// -------------------------------
|
||||
function SetFakeRecordingImpl(fakeRecordingsImpl) {
|
||||
fakeJamClientRecordings = fakeRecordingsImpl;
|
||||
}
|
||||
|
||||
|
||||
// Javascript Bridge seems to camel-case
|
||||
// Set the instance functions:
|
||||
this.AbortRecording = AbortRecording;
|
||||
this.GetASIODevices = GetASIODevices;
|
||||
this.GetOS = GetOS;
|
||||
this.GetOSAsString = GetOSAsString;
|
||||
|
|
@ -548,6 +601,7 @@
|
|||
this.SessionAddTrack = SessionAddTrack;
|
||||
this.SessionGetControlState = SessionGetControlState;
|
||||
this.SessionGetIDs = SessionGetIDs;
|
||||
this.RegisterRecordingCallbacks = RegisterRecordingCallbacks;
|
||||
this.SessionRegisterCallback = SessionRegisterCallback;
|
||||
this.SessionSetAlertCallback = SessionSetAlertCallback;
|
||||
this.SessionSetControlState = SessionSetControlState;
|
||||
|
|
@ -596,6 +650,10 @@
|
|||
this.ClientUpdateStartUpdate = ClientUpdateStartUpdate;
|
||||
|
||||
this.OpenSystemBrowser = OpenSystemBrowser;
|
||||
|
||||
// fake calls; not a part of the actual jam client
|
||||
this.RegisterP2PMessageCallbacks = RegisterP2PMessageCallbacks;
|
||||
this.SetFakeRecordingImpl = SetFakeRecordingImpl;
|
||||
};
|
||||
|
||||
})(window,jQuery);
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
(function(context,$) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.FakeJamClientMessages = function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
function startRecording(recordingId) {
|
||||
var msg = {};
|
||||
msg.type = self.Types.START_RECORDING;
|
||||
msg.msgId = context.JK.generateUUID();
|
||||
msg.recordingId = recordingId;
|
||||
return msg;
|
||||
}
|
||||
|
||||
function startRecordingAck(recordingId, success, reason, detail) {
|
||||
var msg = {};
|
||||
msg.type = self.Types.START_RECORDING_ACK;
|
||||
msg.msgId = context.JK.generateUUID();
|
||||
msg.recordingId = recordingId;
|
||||
msg.success = success;
|
||||
msg.reason = reason;
|
||||
msg.detail = detail;
|
||||
return msg;
|
||||
}
|
||||
|
||||
function stopRecording(recordingId, success, reason, detail) {
|
||||
var msg = {};
|
||||
msg.type = self.Types.STOP_RECORDING;
|
||||
msg.msgId = context.JK.generateUUID();
|
||||
msg.recordingId = recordingId;
|
||||
msg.success = success === undefined ? true : success;
|
||||
msg.reason = reason;
|
||||
msg.detail = detail;
|
||||
return msg;
|
||||
}
|
||||
|
||||
function stopRecordingAck(recordingId, success, reason, detail) {
|
||||
var msg = {};
|
||||
msg.type = self.Types.STOP_RECORDING_ACK;
|
||||
msg.msgId = context.JK.generateUUID();
|
||||
msg.recordingId = recordingId;
|
||||
msg.success = success;
|
||||
msg.reason = reason;
|
||||
msg.detail = detail;
|
||||
return msg;
|
||||
}
|
||||
|
||||
function abortRecording(recordingId, reason, detail) {
|
||||
var msg = {};
|
||||
msg.type = self.Types.ABORT_RECORDING;
|
||||
msg.msgId = context.JK.generateUUID();
|
||||
msg.recordingId = recordingId;
|
||||
msg.success = false;
|
||||
msg.reason = reason;
|
||||
msg.detail = detail;
|
||||
return msg;
|
||||
}
|
||||
|
||||
this.Types = {};
|
||||
this.Types.START_RECORDING = 'start_recording';
|
||||
this.Types.START_RECORDING_ACK = 'start_recording_ack';
|
||||
this.Types.STOP_RECORDING = 'stop_recording;'
|
||||
this.Types.STOP_RECORDING_ACK = 'stop_recording_ack';
|
||||
this.Types.ABORT_RECORDING = 'abort_recording';
|
||||
|
||||
this.startRecording = startRecording;
|
||||
this.startRecordingAck = startRecordingAck;
|
||||
this.stopRecording = stopRecording;
|
||||
this.stopRecordingAck = stopRecordingAck;
|
||||
this.abortRecording = abortRecording;
|
||||
}
|
||||
|
||||
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
// this code simulates what the actual backend recording feature will do
|
||||
(function(context, $) {
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.FakeJamClientRecordings = function(app, fakeJamClient, p2pMessageFactory) {
|
||||
|
||||
var logger = context.JK.logger;
|
||||
|
||||
var startRecordingResultCallbackName = null;
|
||||
var stopRecordingResultCallbackName = null;
|
||||
var startedRecordingResultCallbackName = null;
|
||||
var stoppedRecordingEventCallbackName = null;
|
||||
var abortedRecordingEventCallbackName = null;
|
||||
|
||||
var startingSessionState = null;
|
||||
var stoppingSessionState = null;
|
||||
|
||||
var currentRecordingId = null;
|
||||
var currentRecordingCreatorClientId = null;
|
||||
var currentRecordingClientIds = null;
|
||||
|
||||
function timeoutStartRecordingTimer() {
|
||||
eval(startRecordingResultCallbackName).call(this, startingSessionState.recordingId, {success:false, reason:'client-no-response', detail:startingSessionState.groupedClientTracks[0]});
|
||||
startingSessionState = null;
|
||||
}
|
||||
|
||||
function timeoutStopRecordingTimer() {
|
||||
eval(stopRecordingResultCallbackName).call(this, stoppingSessionState.recordingId, {success:false, reason:'client-no-response', detail:stoppingSessionState.groupedClientTracks[0]});
|
||||
}
|
||||
|
||||
function StartRecording(recordingId, clients) {
|
||||
startingSessionState = {};
|
||||
|
||||
// we expect all clients to respond within 3 seconds to mimic the reliable UDP layer
|
||||
startingSessionState.aggegratingStartResultsTimer = setTimeout(timeoutStartRecordingTimer, 3000);
|
||||
startingSessionState.recordingId = recordingId;
|
||||
startingSessionState.groupedClientTracks = copyClientIds(clients, app.clientId); // we will manipulate this new one
|
||||
|
||||
// store the current recording's data
|
||||
currentRecordingId = recordingId;
|
||||
currentRecordingCreatorClientId = app.clientId;
|
||||
currentRecordingClientIds = copyClientIds(clients, app.clientId);
|
||||
|
||||
if(startingSessionState.groupedClientTracks.length == 0) {
|
||||
// if there are no clients but 'self', then you can declare a successful recording immediately
|
||||
finishSuccessfulStart(recordingId);
|
||||
}
|
||||
else {
|
||||
// signal all other connected clients that the recording has started
|
||||
for(var i = 0; i < startingSessionState.groupedClientTracks.length; i++) {
|
||||
var clientId = startingSessionState.groupedClientTracks[i];
|
||||
context.JK.JamServer.sendP2PMessage(clientId, JSON.stringify(p2pMessageFactory.startRecording(recordingId)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function StopRecording(recordingId, clients, result) {
|
||||
|
||||
if(startingSessionState) {
|
||||
// we are currently starting a session.
|
||||
// TODO
|
||||
}
|
||||
|
||||
if(!result) {
|
||||
result = {success:true}
|
||||
}
|
||||
|
||||
stoppingSessionState = {};
|
||||
|
||||
// we expect all clients to respond within 3 seconds to mimic the reliable UDP layer
|
||||
stoppingSessionState.aggegratingStopResultsTimer = setTimeout(timeoutStopRecordingTimer, 3000);
|
||||
stoppingSessionState.recordingId = recordingId;
|
||||
stoppingSessionState.groupedClientTracks = copyClientIds(clients, app.clientId);
|
||||
|
||||
if(stoppingSessionState.groupedClientTracks.length == 0) {
|
||||
finishSuccessfulStop(recordingId);
|
||||
}
|
||||
else {
|
||||
// signal all other connected clients that the recording has stopped
|
||||
for(var i = 0; i < stoppingSessionState.groupedClientTracks.length; i++) {
|
||||
var clientId = stoppingSessionState.groupedClientTracks[i];
|
||||
context.JK.JamServer.sendP2PMessage(clientId, JSON.stringify(p2pMessageFactory.stopRecording(recordingId, result.success, result.reason, result.detail)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function AbortRecording(recordingId, errorReason, errorDetail) {
|
||||
// todo check recordingId
|
||||
context.JK.JamServer.sendP2PMessage(currentRecordingCreatorClientId, JSON.stringify(p2pMessageFactory.abortRecording(recordingId, errorReason, errorDetail)));
|
||||
}
|
||||
|
||||
function onStartRecording(from, payload) {
|
||||
logger.debug("received start recording request from " + from);
|
||||
if(context.JK.CurrentSessionModel.recordingModel.isRecording()) {
|
||||
// reject the request to start the recording
|
||||
context.JK.JamServer.sendP2PMessage(from, JSON.stringify(p2pMessageFactory.startRecordingAck(payload.recordingId, false, "already-recording", null)));
|
||||
}
|
||||
else {
|
||||
// accept the request, and then tell the frontend we are now recording
|
||||
// a better client implementation would verify that the tracks specified match that what we have configured currently
|
||||
|
||||
// store the current recording's data
|
||||
currentRecordingId = payload.recordingId;
|
||||
currentRecordingCreatorClientId = from;
|
||||
|
||||
context.JK.JamServer.sendP2PMessage(from, JSON.stringify(p2pMessageFactory.startRecordingAck(payload.recordingId, true, null, null)));
|
||||
eval(startedRecordingResultCallbackName).call(this, payload.recordingId, {success:true}, from);
|
||||
}
|
||||
}
|
||||
|
||||
function onStartRecordingAck(from, payload) {
|
||||
logger.debug("received start recording ack from " + from);
|
||||
|
||||
// we should check transactionId; this could be an ACK for a different recording
|
||||
if(startingSessionState) {
|
||||
|
||||
if(payload.success) {
|
||||
var index = startingSessionState.groupedClientTracks.indexOf(from);
|
||||
startingSessionState.groupedClientTracks.splice(index, 1);
|
||||
|
||||
if(startingSessionState.groupedClientTracks.length == 0) {
|
||||
finishSuccessfulStart(payload.recordingId);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TOOD: a client responded with error; we need to tell all other clients to abandon recording
|
||||
logger.warn("received an unsuccessful start_record_ack from: " + from);
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.warn("received a start_record_ack when there is no recording starting from: " + from);
|
||||
// TODO: this is an error case; we should signal back to the sender that we gave up
|
||||
}
|
||||
}
|
||||
|
||||
function onStopRecording(from, payload) {
|
||||
logger.debug("received stop recording request from " + from);
|
||||
|
||||
// TODO check recordingId, and if currently recording
|
||||
// we should return success if we are currently recording, or if we were already asked to stop for this recordingId
|
||||
// this means we should keep a list of the last N recordings that we've seen, rather than just keeping the current
|
||||
context.JK.JamServer.sendP2PMessage(from, JSON.stringify(p2pMessageFactory.stopRecordingAck(payload.recordingId, true)));
|
||||
|
||||
eval(stopRecordingResultCallbackName).call(this, payload.recordingId, {success:payload.success, reason:payload.reason, detail:from});
|
||||
}
|
||||
|
||||
function onStopRecordingAck(from, payload) {
|
||||
logger.debug("received stop recording ack from " + from);
|
||||
|
||||
// we should check transactionId; this could be an ACK for a different recording
|
||||
if(stoppingSessionState) {
|
||||
|
||||
if(payload.success) {
|
||||
var index = stoppingSessionState.groupedClientTracks.indexOf(from);
|
||||
stoppingSessionState.groupedClientTracks.splice(index, 1);
|
||||
|
||||
if(stoppingSessionState.groupedClientTracks.length == 0) {
|
||||
finishSuccessfulStop(payload.recordingId);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TOOD: a client responded with error; what now?
|
||||
logger.error("client responded with error: ", payload);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: this is an error case; we should tell the caller we have no recording at the moment
|
||||
}
|
||||
}
|
||||
|
||||
function onAbortRecording(from, payload) {
|
||||
logger.debug("received abort recording from " + from);
|
||||
|
||||
// TODO check if currently recording and if matches payload.recordingId
|
||||
|
||||
// if creator, tell everyone else to stop
|
||||
if(app.clientId == currentRecordingCreatorClientId) {
|
||||
// ask the front end to stop the recording because it has the full track listing
|
||||
for(var i = 0; i < currentRecordingClientIds.length; i++) {
|
||||
var clientId = currentRecordingClientIds[i];
|
||||
context.JK.JamServer.sendP2PMessage(clientId, JSON.stringify(p2pMessageFactory.abortRecording(currentRecordingId, payload.reason, from)));
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
logger.debug("only the creator currently deals with the abort request. abort request sent from:" + from + " with a reason of: " + payload.errorReason);
|
||||
}
|
||||
|
||||
eval(abortedRecordingEventCallbackName).call(this, payload.recordingId, {success:payload.success, reason:payload.reason, detail:from});
|
||||
}
|
||||
|
||||
function RegisterRecordingCallbacks(startRecordingCallbackName,
|
||||
stopRecordingCallbackName,
|
||||
startedRecordingCallbackName,
|
||||
stoppedRecordingCallbackName,
|
||||
abortedRecordingCallbackName) {
|
||||
startRecordingResultCallbackName = startRecordingCallbackName;
|
||||
stopRecordingResultCallbackName = stopRecordingCallbackName;
|
||||
startedRecordingResultCallbackName = startedRecordingCallbackName;
|
||||
stoppedRecordingEventCallbackName = stoppedRecordingCallbackName;
|
||||
abortedRecordingEventCallbackName = abortedRecordingCallbackName;
|
||||
}
|
||||
|
||||
// copies all clientIds, but removes current client ID because we don't want to message that user
|
||||
function copyClientIds(clientIds, myClientId) {
|
||||
var newClientIds = [];
|
||||
for(var i = 0; i < clientIds.length; i++) {
|
||||
var clientId = clientIds[i]
|
||||
if(clientId != myClientId) {
|
||||
newClientIds.push(clientId);
|
||||
}
|
||||
}
|
||||
return newClientIds;
|
||||
}
|
||||
|
||||
function finishSuccessfulStart(recordingId) {
|
||||
// all clients have responded.
|
||||
clearTimeout(startingSessionState.aggegratingStartResultsTimer);
|
||||
startingSessionState = null;
|
||||
eval(startRecordingResultCallbackName).call(this, recordingId, {success:true});
|
||||
}
|
||||
|
||||
function finishSuccessfulStop(recordingId, errorReason) {
|
||||
// all clients have responded.
|
||||
clearTimeout(stoppingSessionState.aggegratingStopResultsTimer);
|
||||
stoppingSessionState = null;
|
||||
var result = { success: true }
|
||||
if(errorReason)
|
||||
{
|
||||
result.success = false;
|
||||
result.reason = errorReason
|
||||
result.detail = ""
|
||||
}
|
||||
eval(stopRecordingResultCallbackName).call(this, recordingId, result);
|
||||
}
|
||||
|
||||
|
||||
// register for p2p callbacks
|
||||
var callbacks = {};
|
||||
callbacks[p2pMessageFactory.Types.START_RECORDING] = onStartRecording;
|
||||
callbacks[p2pMessageFactory.Types.START_RECORDING_ACK] = onStartRecordingAck;
|
||||
callbacks[p2pMessageFactory.Types.STOP_RECORDING] = onStopRecording;
|
||||
callbacks[p2pMessageFactory.Types.STOP_RECORDING_ACK] = onStopRecordingAck;
|
||||
callbacks[p2pMessageFactory.Types.ABORT_RECORDING] = onAbortRecording;
|
||||
fakeJamClient.RegisterP2PMessageCallbacks(callbacks);
|
||||
this.StartRecording = StartRecording;
|
||||
this.StopRecording = StopRecording;
|
||||
this.AbortRecording = AbortRecording;
|
||||
this.RegisterRecordingCallbacks = RegisterRecordingCallbacks;
|
||||
}
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -253,6 +253,16 @@
|
|||
jamClient.TrackSetChatEnable(false);
|
||||
}
|
||||
|
||||
var defaultInstrumentId;
|
||||
if (context.JK.userMe.instruments && context.JK.userMe.instruments.length > 0) {
|
||||
defaultInstrumentId = context.JK.instrument_id_to_instrument[context.JK.userMe.instruments[0].instrument_id].client_id;
|
||||
}
|
||||
else {
|
||||
defaultInstrumentId = context.JK.server_to_client_instrument_map['Other'].client_id;
|
||||
}
|
||||
|
||||
jamClient.TrackSetInstrument(1, defaultInstrumentId);
|
||||
|
||||
logger.debug("Calling FTUESave(" + persist + ")");
|
||||
var response = jamClient.FTUESave(persist);
|
||||
setLevels(0);
|
||||
|
|
@ -260,7 +270,6 @@
|
|||
logger.warn(response);
|
||||
// TODO - we may need to do something about errors on save.
|
||||
// per VRFS-368, I'm hiding the alert, and logging a warning.
|
||||
// context.alert(response);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Aborting FTUESave as we need input + output selected.");
|
||||
|
|
@ -684,13 +693,10 @@
|
|||
}
|
||||
|
||||
function setAsioSettingsVisibility() {
|
||||
logger.debug("jamClient.FTUEHasControlPanel()=" + jamClient.FTUEHasControlPanel());
|
||||
if (jamClient.FTUEHasControlPanel()) {
|
||||
logger.debug("Showing ASIO button");
|
||||
$('#btn-asio-control-panel').show();
|
||||
}
|
||||
else {
|
||||
logger.debug("Hiding ASIO button");
|
||||
$('#btn-asio-control-panel').hide();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,4 +72,17 @@
|
|||
240: { "server_id": "mandolin" },
|
||||
250: { "server_id": "other" }
|
||||
};
|
||||
|
||||
context.JK.instrument_id_to_instrument = {};
|
||||
|
||||
(function() {
|
||||
$.each(context.JK.server_to_client_instrument_map, function(key, value) {
|
||||
context.JK.instrument_id_to_instrument[value.server_id] = { client_id: value.client_id, display: key }
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
context.JK.entityToPrintable = {
|
||||
music_session: "music session"
|
||||
}
|
||||
})(window,jQuery);
|
||||
|
|
@ -13,7 +13,6 @@
|
|||
var logger = context.JK.logger;
|
||||
|
||||
function createJoinRequest(joinRequest) {
|
||||
logger.debug("joinRequest=" + JSON.stringify(joinRequest));
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
|
|
@ -316,6 +315,53 @@
|
|||
});
|
||||
}
|
||||
|
||||
function startRecording(options) {
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/recordings/start",
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function stopRecording(options) {
|
||||
var recordingId = options["id"]
|
||||
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/recordings/" + recordingId + "/stop",
|
||||
data: JSON.stringify(options)
|
||||
})
|
||||
}
|
||||
|
||||
function getRecording(options) {
|
||||
var recordingId = options["id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
url: "/api/recordings/" + recordingId
|
||||
})
|
||||
}
|
||||
|
||||
function putTrackSyncChange(options) {
|
||||
var musicSessionId = options["id"]
|
||||
delete options["id"];
|
||||
|
||||
return $.ajax({
|
||||
type: "PUT",
|
||||
dataType: "json",
|
||||
url: '/api/sessions/' + musicSessionId + '/tracks',
|
||||
contentType: 'application/json',
|
||||
processData: false,
|
||||
data: JSON.stringify(options)
|
||||
});
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
return self;
|
||||
}
|
||||
|
|
@ -346,6 +392,10 @@
|
|||
this.createJoinRequest = createJoinRequest;
|
||||
this.updateJoinRequest = updateJoinRequest;
|
||||
this.updateUser = updateUser;
|
||||
this.startRecording = startRecording;
|
||||
this.stopRecording = stopRecording;
|
||||
this.getRecording = getRecording;
|
||||
this.putTrackSyncChange = putTrackSyncChange;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -175,7 +175,38 @@
|
|||
*/
|
||||
function ajaxError(jqXHR, textStatus, errorMessage) {
|
||||
logger.error("Unexpected ajax error: " + textStatus);
|
||||
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
|
||||
|
||||
if(jqXHR.status == 404) {
|
||||
app.notify({title: "Oops!", text: "What you were looking for is gone now."});
|
||||
}
|
||||
else if(jqXHR.status = 422) {
|
||||
// present a nicer message
|
||||
try {
|
||||
var text = "<ul>";
|
||||
var errorResponse = JSON.parse(jqXHR.responseText)["errors"];
|
||||
for(var key in errorResponse) {
|
||||
var errorsForKey = errorResponse[key];
|
||||
console.log("key: " + key);
|
||||
var prettyKey = context.JK.entityToPrintable[key];
|
||||
if(!prettyKey) { prettyKey = key; }
|
||||
for(var i = 0; i < errorsForKey.length; i++) {
|
||||
|
||||
text += "<li>" + prettyKey + " " + errorsForKey[i] + "</li>";
|
||||
}
|
||||
}
|
||||
|
||||
text += "<ul>";
|
||||
|
||||
app.notify({title: "Oops!", text: text, "icon_url": "/assets/content/icon_alert_big.png"});
|
||||
}
|
||||
catch(e) {
|
||||
// give up; not formatted correctly
|
||||
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
|
||||
}
|
||||
}
|
||||
else {
|
||||
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -221,6 +252,11 @@
|
|||
this.layout.notify(message, descriptor);
|
||||
};
|
||||
|
||||
/** Shows an alert notification. Expects text, title */
|
||||
this.notifyAlert = function(title ,text) {
|
||||
this.notify({title:title, text:text, icon_url: "/assets/content/icon_alert_big.png"});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize any common events.
|
||||
*/
|
||||
|
|
@ -264,6 +300,7 @@
|
|||
|
||||
if (context.jamClient) {
|
||||
// Unregister for callbacks.
|
||||
context.jamClient.RegisterRecordingCallbacks("", "", "", "", "");
|
||||
context.jamClient.SessionRegisterCallback("");
|
||||
context.jamClient.SessionSetAlertCallback("");
|
||||
context.jamClient.FTUERegisterVUCallbacks("", "", "");
|
||||
|
|
|
|||
|
|
@ -439,9 +439,13 @@
|
|||
function dialogEvent(dialog, evtName, data) {
|
||||
if (dialog && dialog in dialogBindings) {
|
||||
if (evtName in dialogBindings[dialog]) {
|
||||
dialogBindings[dialog][evtName].call(me, data);
|
||||
var result = dialogBindings[dialog][evtName].call(me, data);
|
||||
if(result === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function changeToScreen(screen, data) {
|
||||
|
|
@ -483,12 +487,20 @@
|
|||
* also moves the .dialog-overlay such that it hides/obscures all dialogs except the highest one
|
||||
*/
|
||||
function stackDialogs($dialog, $overlay) {
|
||||
console.log("pushing dialog: " + $dialog.attr('layout-id'))
|
||||
// don't push a dialog on the stack that is already on there; remove it from where ever it is currently
|
||||
// and the rest of the code will make it end up at the top
|
||||
var layoutId = $dialog.attr('layout-id');
|
||||
for(var i = openDialogs.length - 1; i >= 0; i--) {
|
||||
if(openDialogs[i].attr('layout-id') === layoutId) {
|
||||
openDialogs.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
openDialogs.push($dialog);
|
||||
var zIndex = 1000;
|
||||
for(var i in openDialogs) {
|
||||
var $dialog = openDialogs[i];
|
||||
$dialog.css('zIndex', zIndex);
|
||||
var $openDialog = openDialogs[i];
|
||||
$openDialog.css('zIndex', zIndex);
|
||||
zIndex++;
|
||||
}
|
||||
$overlay.css('zIndex', zIndex - 1);
|
||||
|
|
@ -496,11 +508,7 @@
|
|||
|
||||
function unstackDialogs($overlay) {
|
||||
if(openDialogs.length > 0) {
|
||||
var removed = openDialogs.pop();
|
||||
console.log("removed dialog : " + removed.attr('layout-id'));
|
||||
}
|
||||
else {
|
||||
console.log("no dialog removed because nothing was on the stack");
|
||||
openDialogs.pop();
|
||||
}
|
||||
|
||||
var zIndex = 1000 + openDialogs.length;
|
||||
|
|
@ -512,7 +520,7 @@
|
|||
}
|
||||
|
||||
function showDialog(dialog) {
|
||||
dialogEvent(dialog, 'beforeShow');
|
||||
if(!dialogEvent(dialog, 'beforeShow')) {return;}
|
||||
var $overlay = $('.dialog-overlay')
|
||||
$overlay.show();
|
||||
centerDialog(dialog);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,333 @@
|
|||
// The recording must be fed certain events, and as a simplification to consumers, it will emit state engine transition events.
|
||||
// This class automatically watches for server notifications relating to recordings (start/stop), as well as backend events
|
||||
// inputs:
|
||||
// * startRecording: user wants to start a recording
|
||||
// * stopRecording: user wants to stop recording
|
||||
//
|
||||
// events:
|
||||
// * startingRecording: a recording has been requested, but isn't confirmed started
|
||||
// * startedRecording: a recording is officially started
|
||||
// * stoppingRecording: a stop to the current recording has been requested, but it isn't confirmed yet
|
||||
// * stoppedRecording: a recording is not running
|
||||
// *
|
||||
|
||||
(function(context,$) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
var logger = context.JK.logger;
|
||||
|
||||
context.JK.RecordingModel = function(app, sessionModel, _rest, _jamClient) {
|
||||
var currentRecording = null; // the JSON response from the server for a recording
|
||||
var currentRecordingId = null;
|
||||
var rest = _rest;
|
||||
var currentlyRecording = false;
|
||||
var startingRecording = false;
|
||||
var stoppingRecording = false;
|
||||
var waitingOnServerStop = false;
|
||||
var waitingOnClientStop = false;
|
||||
var waitingOnStopTimer = null;
|
||||
var jamClient = _jamClient;
|
||||
|
||||
var sessionModel = sessionModel;
|
||||
var $self = $(this);
|
||||
|
||||
function isRecording (recordingId) {
|
||||
// if you specify recordingId, the check is more exact
|
||||
if(recordingId) {
|
||||
return recordingId == currentRecordingId;
|
||||
}
|
||||
else {
|
||||
// if you omit recordingId, then we'll just check if we are recording at all
|
||||
return currentlyRecording;
|
||||
}
|
||||
}
|
||||
|
||||
/** called every time a session is joined, to ensure clean state */
|
||||
function reset() {
|
||||
currentlyRecording = false;
|
||||
waitingOnServerStop = false;
|
||||
waitingOnClientStop = false;
|
||||
if(waitingOnStopTimer != null) {
|
||||
clearTimeout(waitingOnStopTimer);
|
||||
waitingOnStopTimer = null;
|
||||
}
|
||||
currentRecording = null;
|
||||
currentRecordingId = null;
|
||||
}
|
||||
|
||||
function groupTracksToClient(recording) {
|
||||
// group N tracks to the same client Id
|
||||
var groupedTracks = {};
|
||||
var recordingTracks = recording["recorded_tracks"];
|
||||
for (var i = 0; i < recordingTracks.length; i++) {
|
||||
var clientId = recordingTracks[i].client_id;
|
||||
|
||||
var tracksForClient = groupedTracks[clientId];
|
||||
if (!tracksForClient) {
|
||||
tracksForClient = [];
|
||||
groupedTracks[clientId] = tracksForClient;
|
||||
}
|
||||
tracksForClient.push(recordingTracks[i]);
|
||||
}
|
||||
return context.JK.dkeys(groupedTracks);
|
||||
}
|
||||
|
||||
function startRecording() {
|
||||
|
||||
$self.triggerHandler('startingRecording', {});
|
||||
|
||||
currentlyRecording = true;
|
||||
|
||||
currentRecording = rest.startRecording({"music_session_id": sessionModel.id()})
|
||||
.done(function(recording) {
|
||||
currentRecordingId = recording.id;
|
||||
|
||||
// ask the backend to start the session.
|
||||
var groupedTracks = groupTracksToClient(recording);
|
||||
jamClient.StartRecording(recording["id"], groupedTracks);
|
||||
})
|
||||
.fail(function() {
|
||||
$self.triggerHandler('startedRecording', { clientId: app.clientId, reason: 'rest', detail: arguments });
|
||||
currentlyRecording = false;
|
||||
})
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Nulls can be passed for all 3 currently; that's a user request. */
|
||||
function stopRecording(recordingId, reason, detail) {
|
||||
|
||||
waitingOnServerStop = waitingOnClientStop = true;
|
||||
waitingOnStopTimer = setTimeout(timeoutTransitionToStop, 5000);
|
||||
|
||||
$self.triggerHandler('stoppingRecording', {reason: reason, detail: detail});
|
||||
|
||||
// this path assumes that the currentRecording info has, or can be, retrieved
|
||||
// failure for currentRecording is handled elsewhere
|
||||
currentRecording
|
||||
.done(function(recording) {
|
||||
|
||||
var groupedTracks = groupTracksToClient(recording);
|
||||
|
||||
jamClient.StopRecording(recording.id, groupedTracks);
|
||||
rest.stopRecording( { "id": recording.id } )
|
||||
.done(function() {
|
||||
waitingOnServerStop = false;
|
||||
attemptTransitionToStop(recording.id, reason, detail);
|
||||
})
|
||||
.fail(function(jqXHR) {
|
||||
if(jqXHR.status == 422) {
|
||||
waitingOnServerStop = false;
|
||||
attemptTransitionToStop(recording.id, reason, detail);
|
||||
}
|
||||
else {
|
||||
logger.error("unable to stop recording %o", arguments);
|
||||
transitionToStopped();
|
||||
$self.triggerHandler('stoppedRecording', {'recordingId': recording.id, 'reason' : 'rest', 'details' : arguments});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function abortRecording(recordingId, errorReason, errorDetail) {
|
||||
jamClient.AbortRecording(recordingId, {reason: errorReason, detail: errorDetail, success:false});
|
||||
}
|
||||
|
||||
function timeoutTransitionToStop() {
|
||||
// doh. couldn't stop
|
||||
waitingOnStopTimer = null;
|
||||
transitionToStopped();
|
||||
$self.triggerHandler('stoppedRecordingFailed', { 'reason' : 'timeout' });
|
||||
}
|
||||
|
||||
// Only tell the user that we've stopped once both server and client agree we've stopped
|
||||
function attemptTransitionToStop(recordingId, errorReason, errorDetail) {
|
||||
if(!waitingOnClientStop && !waitingOnServerStop) {
|
||||
transitionToStopped();
|
||||
$self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: errorReason, detail: errorDetail});
|
||||
}
|
||||
}
|
||||
|
||||
function transitionToStopped() {
|
||||
currentlyRecording = false;
|
||||
currentRecording = null;
|
||||
currentRecordingId = null;
|
||||
if(waitingOnStopTimer) {
|
||||
clearTimeout(waitingOnStopTimer);
|
||||
waitingOnStopTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function onServerStartRecording() {
|
||||
|
||||
}
|
||||
|
||||
function onServerStopRecording(recordingId) {
|
||||
stopRecording(recordingId, null, null);
|
||||
}
|
||||
|
||||
function handleRecordingStartResult(recordingId, result) {
|
||||
|
||||
var success = result.success;
|
||||
var reason = result.reason;
|
||||
var detail = result.detail;
|
||||
|
||||
|
||||
if(success) {
|
||||
$self.triggerHandler('startedRecording', {clientId: app.clientId})
|
||||
}
|
||||
else {
|
||||
currentlyRecording = false;
|
||||
logger.error("unable to start the recording %o, %o", reason, detail);
|
||||
$self.triggerHandler('startedRecording', { clientId: app.clientId, reason: reason, detail: detail});
|
||||
}
|
||||
}
|
||||
|
||||
function handleRecordingStopResult(recordingId, result) {
|
||||
|
||||
var success = result.success;
|
||||
var reason = result.reason;
|
||||
var detail = result.detail;
|
||||
|
||||
waitingOnClientStop = false;
|
||||
|
||||
if(success) {
|
||||
attemptTransitionToStop(recordingId, reason, detail);
|
||||
}
|
||||
else {
|
||||
transitionToStopped();
|
||||
logger.error("backend unable to stop the recording %o, %o", reason, detail);
|
||||
$self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: reason, detail : detail});
|
||||
}
|
||||
}
|
||||
|
||||
function handleRecordingStarted(recordingId, result, clientId) {
|
||||
var success = result.success;
|
||||
var reason = result.reason;
|
||||
var detail = result.detail;
|
||||
|
||||
// in this scenario, we don't know all the tracks of the user.
|
||||
// we need to ask sessionModel to populate us with the recording data ASAP
|
||||
|
||||
currentRecording = rest.getRecording({id: recordingId})
|
||||
.fail(function() {
|
||||
abortRecording(recordingId, 'populate-recording-info', app.clientId);
|
||||
})
|
||||
.done(function(recording) {
|
||||
currentRecordingId = recording.id;
|
||||
});
|
||||
|
||||
$self.triggerHandler('startingRecording', {recordingId: recordingId});
|
||||
currentlyRecording = true;
|
||||
$self.triggerHandler('startedRecording', {clientId: clientId, recordingId: recordingId});
|
||||
}
|
||||
|
||||
function handleRecordingStopped(recordingId, result) {
|
||||
var success = result.success;
|
||||
var reason = result.reason;
|
||||
var detail = result.detail;
|
||||
|
||||
|
||||
$self.triggerHandler('stoppingRecording', {recordingId: recordingId, reason: reason, detail: detail });
|
||||
// the backend says the recording must be stopped.
|
||||
// tell the server to stop it too
|
||||
rest.stopRecording({
|
||||
id: recordingId
|
||||
})
|
||||
.always(function() {
|
||||
transitionToStopped();
|
||||
})
|
||||
.fail(function(jqXHR, textStatus, errorMessage) {
|
||||
if(jqXHR.status == 422) {
|
||||
logger.debug("recording already stopped %o", arguments);
|
||||
$self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: reason, detail: detail});
|
||||
}
|
||||
else if(jqXHR.status == 404) {
|
||||
logger.debug("recording is already deleted %o", arguments);
|
||||
$self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: reason, detail: detail});
|
||||
}
|
||||
else {
|
||||
$self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: textStatus, detail: errorMessage});
|
||||
}
|
||||
})
|
||||
.done(function() {
|
||||
$self.triggerHandler('stoppedRecording', {recordingId: recordingId, reason: reason, detail: detail});
|
||||
})
|
||||
}
|
||||
|
||||
function handleRecordingAborted(recordingId, result) {
|
||||
var success = result.success;
|
||||
var reason = result.reason;
|
||||
var detail = result.detail;
|
||||
|
||||
stoppingRecording = false;
|
||||
|
||||
$self.triggerHandler('abortedRecording', {recordingId: recordingId, reason: reason, detail: detail });
|
||||
// the backend says the recording must be stopped.
|
||||
// tell the server to stop it too
|
||||
rest.stopRecording({
|
||||
id: recordingId
|
||||
})
|
||||
.always(function() {
|
||||
currentlyRecording = false;
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* If a stop is needed, it will be issued, and the deferred object will fire done()
|
||||
* If a stop is not needed (i.e., there is no recording), then the deferred object will fire immediately
|
||||
* @returns {$.Deferred} in all cases, only .done() is fired.
|
||||
*/
|
||||
function stopRecordingIfNeeded() {
|
||||
var deferred = new $.Deferred();
|
||||
|
||||
function resolved() {
|
||||
$self.off('stoppedRecording.stopRecordingIfNeeded', resolved);
|
||||
deferred.resolve(arguments);
|
||||
}
|
||||
|
||||
if(!currentlyRecording) {
|
||||
deferred = new $.Deferred();
|
||||
deferred.resolve();
|
||||
}
|
||||
else {
|
||||
// wait for the next stoppedRecording event message
|
||||
$self.on('stoppedRecording.stopRecordingIfNeeded', resolved);
|
||||
|
||||
if(!stopRecording()) {
|
||||
// no event is coming, so satisfy the deferred immediately
|
||||
$self.off('stoppedRecording.stopRecordingIfNeeded', resolved);
|
||||
deferred = new $.Deferred();
|
||||
deferred.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
return deferred;
|
||||
|
||||
}
|
||||
|
||||
this.initialize = function() {
|
||||
};
|
||||
|
||||
this.startRecording = startRecording;
|
||||
this.stopRecording = stopRecording;
|
||||
this.onServerStopRecording = onServerStopRecording;
|
||||
this.isRecording = isRecording;
|
||||
this.reset = reset;
|
||||
this.stopRecordingIfNeeded = stopRecordingIfNeeded;
|
||||
|
||||
context.JK.HandleRecordingStartResult = handleRecordingStartResult;
|
||||
context.JK.HandleRecordingStopResult = handleRecordingStopResult;
|
||||
context.JK.HandleRecordingStopped = handleRecordingStopped;
|
||||
context.JK.HandleRecordingStarted = handleRecordingStarted;
|
||||
context.JK.HandleRecordingAborted = handleRecordingAborted;
|
||||
|
||||
|
||||
};
|
||||
|
||||
})(window,jQuery);
|
||||
|
|
@ -10,18 +10,21 @@
|
|||
var tracks = {};
|
||||
var myTracks = [];
|
||||
var mixers = [];
|
||||
|
||||
var configureTrackDialog;
|
||||
var addNewGearDialog;
|
||||
|
||||
var screenActive = false;
|
||||
|
||||
var currentMixerRangeMin = null;
|
||||
var currentMixerRangeMax = null;
|
||||
|
||||
var lookingForMixersCount = 0;
|
||||
var lookingForMixersTimer = null;
|
||||
var lookingForMixers = {};
|
||||
var $recordingTimer = null;
|
||||
var recordingTimerInterval = null;
|
||||
var startTimeDate = null;
|
||||
var startingRecording = false; // double-click guard
|
||||
|
||||
|
||||
var rest = JK.Rest();
|
||||
|
||||
var RENDER_SESSION_DELAY = 750; // When I need to render a session, I have to wait a bit for the mixers to be there.
|
||||
|
||||
|
|
@ -92,18 +95,59 @@
|
|||
function beforeShow(data) {
|
||||
sessionId = data.id;
|
||||
$('#session-mytracks-container').empty();
|
||||
displayDoneRecording(); // assumption is that you can't join a recording session, so this should be safe
|
||||
}
|
||||
|
||||
function alertCallback(type, text) {
|
||||
if (type === 2) { // BACKEND_MIXER_CHANGE
|
||||
sessionModel.refreshCurrentSession();
|
||||
logger.debug("BACKEND_MIXER_CHANGE alert. reason:" + text);
|
||||
if(sessionModel.id() && text == "RebuildAudioIoControl") {
|
||||
// this is a local change to our tracks. we need to tell the server about our updated track information
|
||||
var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient);
|
||||
|
||||
// create a trackSync request based on backend data
|
||||
var syncTrackRequest = {};
|
||||
syncTrackRequest.client_id = app.clientId;
|
||||
syncTrackRequest.tracks = inputTracks;
|
||||
syncTrackRequest.id = sessionModel.id();
|
||||
|
||||
rest.putTrackSyncChange(syncTrackRequest)
|
||||
.done(function() {
|
||||
sessionModel.refreshCurrentSession();
|
||||
})
|
||||
.fail(function() {
|
||||
app.notify({
|
||||
"title": "Can't Sync Local Tracks",
|
||||
"text": "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience.",
|
||||
"icon_url": "/assets/content/icon_alert_big.png"
|
||||
});
|
||||
})
|
||||
}
|
||||
else {
|
||||
// this is wrong; we shouldn't be using the server to decide what tracks are shown
|
||||
// however, we have to without a refactor, and if we wait a second, then it should be enough time
|
||||
// for the client that initialed this to have made the change
|
||||
// https://jamkazam.atlassian.net/browse/VRFS-854
|
||||
setTimeout(function() {
|
||||
sessionModel.refreshCurrentSession(); // XXX: race condition possible here; other client may not have updated server yet
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
context.setTimeout(function() {
|
||||
app.notify({
|
||||
"title": alert_type[type].title,
|
||||
"text": text,
|
||||
"icon_url": "/assets/content/icon_alert_big.png"
|
||||
}); }, 1);
|
||||
var alert = alert_type[type];
|
||||
|
||||
if(alert) {
|
||||
app.notify({
|
||||
"title": alert_type[type].title,
|
||||
"text": text,
|
||||
"icon_url": "/assets/content/icon_alert_big.png"
|
||||
});
|
||||
}
|
||||
else {
|
||||
logger.debug("Unknown Backend Event type %o, data %o", type, text)
|
||||
}
|
||||
}, 1);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +160,7 @@
|
|||
// Subscribe for callbacks on audio events
|
||||
context.jamClient.RegisterVolChangeCallBack("JK.HandleVolumeChangeCallback");
|
||||
context.jamClient.SessionRegisterCallback("JK.HandleBridgeCallback");
|
||||
context.jamClient.RegisterRecordingCallbacks("JK.HandleRecordingStartResult", "JK.HandleRecordingStopResult", "JK.HandleRecordingStarted", "JK.HandleRecordingStopped", "JK.HandleRecordingAborted");
|
||||
context.jamClient.SessionSetAlertCallback("JK.AlertCallback");
|
||||
|
||||
// If you load this page directly, the loading of the current user
|
||||
|
|
@ -132,8 +177,26 @@
|
|||
checkForCurrentUser();
|
||||
}
|
||||
|
||||
function notifyWithUserInfo(title , text, clientId) {
|
||||
sessionModel.findUserBy({clientId: clientId})
|
||||
.done(function(user) {
|
||||
app.notify({
|
||||
"title": title,
|
||||
"text": user.name + " " + text,
|
||||
"icon_url": context.JK.resolveAvatarUrl(user.photo_url)
|
||||
});
|
||||
})
|
||||
.fail(function() {
|
||||
app.notify({
|
||||
"title": title,
|
||||
"text": 'Someone ' + text,
|
||||
"icon_url": "/assets/content/icon_alert_big.png"
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function afterCurrentUserLoaded() {
|
||||
logger.debug("afterCurrentUserLoaded");
|
||||
// It seems the SessionModel should be a singleton.
|
||||
// a client can only be in one session at a time,
|
||||
// and other parts of the code want to know at any certain times
|
||||
|
|
@ -144,6 +207,130 @@
|
|||
context.jamClient
|
||||
);
|
||||
|
||||
$(sessionModel.recordingModel)
|
||||
.on('startingRecording', function(e, data) {
|
||||
displayStartingRecording();
|
||||
})
|
||||
.on('startedRecording', function(e, data) {
|
||||
if(data.reason) {
|
||||
var reason = data.reason;
|
||||
var detail = data.detail;
|
||||
|
||||
var title = "Could Not Start Recording";
|
||||
|
||||
if(data.reason == 'client-no-response') {
|
||||
notifyWithUserInfo(title, 'did not respond to the start signal.', detail);
|
||||
}
|
||||
else if(data.reason == 'empty-recording-id') {
|
||||
app.notifyAlert(title, "No recording ID specified.");
|
||||
}
|
||||
else if(data.reason == 'missing-client') {
|
||||
notifyWithUserInfo(title, 'could not be signalled to start recording.', detail);
|
||||
}
|
||||
else if(data.reason == 'already-recording') {
|
||||
app.notifyAlert(title, 'Already recording.');
|
||||
}
|
||||
else if(data.reason == 'recording-engine-unspecified') {
|
||||
notifyWithUserInfo(title, 'had a problem writing recording data to disk.', detail);
|
||||
}
|
||||
else if(data.reason == 'recording-engine-create-directory') {
|
||||
notifyWithUserInfo(title, 'had a problem creating a recording folder.', detail);
|
||||
}
|
||||
else if(data.reason == 'recording-engine-create-file') {
|
||||
notifyWithUserInfo(title, 'had a problem creating a recording file.', detail);
|
||||
}
|
||||
else if(data.reason == 'recording-engine-sample-rate') {
|
||||
notifyWithUserInfo(title, 'had a problem recording at the specified sample rate.', detail);
|
||||
}
|
||||
else {
|
||||
notifyWithUserInfo(title, 'Error Reason: ' + reason);
|
||||
}
|
||||
displayDoneRecording();
|
||||
}
|
||||
else
|
||||
{
|
||||
displayStartedRecording();
|
||||
displayWhoCreated(data.clientId);
|
||||
}
|
||||
})
|
||||
.on('stoppingRecording', function(e, data) {
|
||||
displayStoppingRecording(data);
|
||||
})
|
||||
.on('stoppedRecording', function(e, data) {
|
||||
if(data.reason) {
|
||||
var reason = data.reason;
|
||||
var detail = data.detail;
|
||||
|
||||
var title = "Recording Discarded";
|
||||
|
||||
if(data.reason == 'client-no-response') {
|
||||
notifyWithUserInfo(title, 'did not respond to the stop signal.', detail);
|
||||
}
|
||||
else if(data.reason == 'missing-client') {
|
||||
notifyWithUserInfo(title, 'could not be signalled to stop recording.', detail);
|
||||
}
|
||||
else if(data.reason == 'empty-recording-id') {
|
||||
app.notifyAlert(title, "No recording ID specified.");
|
||||
}
|
||||
else if(data.reason == 'wrong-recording-id') {
|
||||
app.notifyAlert(title, "Wrong recording ID specified.");
|
||||
}
|
||||
else if(data.reason == 'not-recording') {
|
||||
app.notifyAlert(title, "Not currently recording.");
|
||||
}
|
||||
else if(data.reason == 'already-stopping') {
|
||||
app.notifyAlert(title, "Already stopping the current recording.");
|
||||
}
|
||||
else if(data.reason == 'start-before-stop') {
|
||||
notifyWithUserInfo(title, 'asked that we start a new recording; cancelling the current one.', detail);
|
||||
}
|
||||
else {
|
||||
app.notifyAlert(title, "Error reason: " + reason);
|
||||
}
|
||||
|
||||
displayDoneRecording();
|
||||
}
|
||||
else {
|
||||
displayDoneRecording();
|
||||
promptUserToSave(data.recordingId);
|
||||
}
|
||||
|
||||
})
|
||||
.on('abortedRecording', function(e, data) {
|
||||
var reason = data.reason;
|
||||
var detail = data.detail;
|
||||
|
||||
var title = "Recording Cancelled";
|
||||
|
||||
if(data.reason == 'client-no-response') {
|
||||
notifyWithUserInfo(title, 'did not respond to the start signal.', detail);
|
||||
}
|
||||
else if(data.reason == 'missing-client') {
|
||||
notifyWithUserInfo(title, 'could not be signalled to start recording.', detail);
|
||||
}
|
||||
else if(data.reason == 'populate-recording-info') {
|
||||
notifyWithUserInfo(title, 'could not synchronize with the server.', detail);
|
||||
}
|
||||
else if(data.reason == 'recording-engine-unspecified') {
|
||||
notifyWithUserInfo(title, 'had a problem writing recording data to disk.', detail);
|
||||
}
|
||||
else if(data.reason == 'recording-engine-create-directory') {
|
||||
notifyWithUserInfo(title, 'had a problem creating a recording folder.', detail);
|
||||
}
|
||||
else if(data.reason == 'recording-engine-create-file') {
|
||||
notifyWithUserInfo(title, 'had a problem creating a recording file.', detail);
|
||||
}
|
||||
else if(data.reason == 'recording-engine-sample-rate') {
|
||||
notifyWithUserInfo(title, 'had a problem recording at the specified sample rate.', detail);
|
||||
}
|
||||
else {
|
||||
app.notifyAlert(title, "Error reason: " + reason);
|
||||
}
|
||||
|
||||
displayDoneRecording();
|
||||
|
||||
})
|
||||
|
||||
sessionModel.subscribe('sessionScreen', sessionChanged);
|
||||
sessionModel.joinSession(sessionId)
|
||||
.fail(function(xhr, textStatus, errorMessage) {
|
||||
|
|
@ -775,10 +962,121 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/questions/2604450/how-to-create-a-jquery-clock-timer
|
||||
function updateRecordingTimer() {
|
||||
|
||||
function pretty_time_string(num) {
|
||||
return ( num < 10 ? "0" : "" ) + num;
|
||||
}
|
||||
|
||||
var total_seconds = (new Date - startTimeDate) / 1000;
|
||||
|
||||
var hours = Math.floor(total_seconds / 3600);
|
||||
total_seconds = total_seconds % 3600;
|
||||
|
||||
var minutes = Math.floor(total_seconds / 60);
|
||||
total_seconds = total_seconds % 60;
|
||||
|
||||
var seconds = Math.floor(total_seconds);
|
||||
|
||||
hours = pretty_time_string(hours);
|
||||
minutes = pretty_time_string(minutes);
|
||||
seconds = pretty_time_string(seconds);
|
||||
|
||||
if(hours > 0) {
|
||||
var currentTimeString = hours + ":" + minutes + ":" + seconds;
|
||||
}
|
||||
else {
|
||||
var currentTimeString = minutes + ":" + seconds;
|
||||
}
|
||||
|
||||
$recordingTimer.text('(' + currentTimeString + ')');
|
||||
}
|
||||
|
||||
function displayStartingRecording() {
|
||||
|
||||
$('#recording-start-stop').addClass('currently-recording');
|
||||
|
||||
$('#recording-status').text("Starting...")
|
||||
}
|
||||
|
||||
function displayStartedRecording() {
|
||||
startTimeDate = new Date;
|
||||
$recordingTimer = $("<span id='recording-timer'>(0:00)</span>");
|
||||
var $recordingStatus = $('<span></span>').append("<span>Stop Recording</span>").append($recordingTimer);
|
||||
$('#recording-status').html( $recordingStatus );
|
||||
recordingTimerInterval = setInterval(updateRecordingTimer, 1000);
|
||||
}
|
||||
|
||||
function displayStoppingRecording(data) {
|
||||
if(data) {
|
||||
if(data.reason) {
|
||||
app.notify({
|
||||
"title": "Recording Aborted",
|
||||
"text": "The recording was aborted due to '" + data.reason + '"',
|
||||
"icon_url": "/assets/content/icon_alert_big.png"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$('#recording-status').text("Stopping...");
|
||||
}
|
||||
|
||||
function displayDoneRecording() {
|
||||
if(recordingTimerInterval) {
|
||||
clearInterval(recordingTimerInterval);
|
||||
recordingTimerInterval = null;
|
||||
startTimeDate = null;
|
||||
}
|
||||
|
||||
$recordingTimer = null;
|
||||
|
||||
$('#recording-start-stop').removeClass('currently-recording');
|
||||
$('#recording-status').text("Make a Recording");
|
||||
}
|
||||
|
||||
function displayWhoCreated(clientId) {
|
||||
if(app.clientId != clientId) { // don't show to creator
|
||||
sessionModel.findUserBy({clientId: clientId})
|
||||
.done(function(user) {
|
||||
app.notify({
|
||||
"title": "Recording Started",
|
||||
"text": user.name + " started a recording",
|
||||
"icon_url": context.JK.resolveAvatarUrl(user.photo_url)
|
||||
});
|
||||
})
|
||||
.fail(function() {
|
||||
app.notify({
|
||||
"title": "Recording Started",
|
||||
"text": "Oops! Can't determine who started this recording",
|
||||
"icon_url": "/assets/content/icon_alert_big.png"
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function promptUserToSave(recordingId) {
|
||||
rest.getRecording( {id: recordingId} )
|
||||
.done(function(recording) {
|
||||
app.layout.showDialog('recordingFinished');
|
||||
})
|
||||
.fail(app.ajaxError);
|
||||
}
|
||||
|
||||
function startStopRecording() {
|
||||
if(sessionModel.recordingModel.isRecording()) {
|
||||
sessionModel.recordingModel.stopRecording();
|
||||
}
|
||||
else {
|
||||
sessionModel.recordingModel.startRecording();
|
||||
}
|
||||
}
|
||||
|
||||
function events() {
|
||||
$('#session-resync').on('click', sessionResync);
|
||||
$('#session-contents').on("click", '[action="delete"]', deleteSession);
|
||||
$('#tracks').on('click', 'div[control="mute"]', toggleMute);
|
||||
$('#recording-start-stop').on('click', startStopRecording)
|
||||
|
||||
$('#track-settings').click(function() {
|
||||
configureTrackDialog.showVoiceChatPanel(true);
|
||||
|
|
@ -811,7 +1109,6 @@
|
|||
context.JK.HandleVolumeChangeCallback = handleVolumeChangeCallback;
|
||||
context.JK.HandleBridgeCallback = handleBridgeCallback;
|
||||
context.JK.AlertCallback = alertCallback;
|
||||
|
||||
};
|
||||
|
||||
})(window,jQuery);
|
||||
|
|
@ -14,9 +14,11 @@
|
|||
var subscribers = {};
|
||||
var users = {}; // User info for session participants
|
||||
var rest = context.JK.Rest();
|
||||
|
||||
var requestingSessionRefresh = false;
|
||||
var pendingSessionRefresh = false;
|
||||
var recordingModel = new context.JK.RecordingModel(app, this, rest, context.jamClient);
|
||||
function id() {
|
||||
return currentSession.id;
|
||||
return currentSession ? currentSession.id : null;
|
||||
}
|
||||
|
||||
function participants() {
|
||||
|
|
@ -63,6 +65,7 @@
|
|||
context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.join);
|
||||
}
|
||||
|
||||
recordingModel.reset();
|
||||
client.JoinSession({ sessionID: sessionId });
|
||||
refreshCurrentSession();
|
||||
server.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
|
||||
|
|
@ -77,40 +80,41 @@
|
|||
return deferred;
|
||||
}
|
||||
|
||||
function performLeaveSession(deferred) {
|
||||
|
||||
logger.debug("SessionModel.leaveCurrentSession()");
|
||||
// TODO - sessionChanged will be called with currentSession = null
|
||||
server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
|
||||
server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession);
|
||||
// leave the session right away without waiting on REST. Why? If you can't contact the server, or if it takes a long
|
||||
// time, for that entire duration you'll still be sending voice data to the other users.
|
||||
// this may be bad if someone decides to badmouth others in the left-session during this time
|
||||
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
|
||||
client.LeaveSession({ sessionID: currentSessionId });
|
||||
deferred = leaveSessionRest(currentSessionId);
|
||||
deferred.done(function() {
|
||||
sessionChanged();
|
||||
});
|
||||
|
||||
// 'unregister' for callbacks
|
||||
context.jamClient.SessionRegisterCallback("");
|
||||
context.jamClient.SessionSetAlertCallback("");
|
||||
updateCurrentSession(null);
|
||||
currentSessionId = null;
|
||||
}
|
||||
/**
|
||||
* Leave the current session, if there is one.
|
||||
* callback: called in all conditions; either after an attempt is made to tell the server that we are leaving,
|
||||
* or immediately if there is no session
|
||||
*/
|
||||
function leaveCurrentSession() {
|
||||
var deferred;
|
||||
var deferred = new $.Deferred();
|
||||
|
||||
if(currentSessionId) {
|
||||
logger.debug("SessionModel.leaveCurrentSession()");
|
||||
// TODO - sessionChanged will be called with currentSession = null
|
||||
server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_JOIN, refreshCurrentSession);
|
||||
server.unregisterMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, refreshCurrentSession);
|
||||
// leave the session right away without waiting on REST. Why? If you can't contact the server, or if it takes a long
|
||||
// time, for that entire duration you'll still be sending voice data to the other users.
|
||||
// this may be bad if someone decides to badmouth others in the left-session during this time
|
||||
logger.debug("calling jamClient.LeaveSession for clientId=" + clientId);
|
||||
client.LeaveSession({ sessionID: currentSessionId });
|
||||
deferred = leaveSessionRest(currentSessionId);
|
||||
deferred.done(function() {
|
||||
sessionChanged();
|
||||
recordingModel.stopRecordingIfNeeded()
|
||||
.always(function(){
|
||||
performLeaveSession(deferred);
|
||||
});
|
||||
|
||||
// 'unregister' for callbacks
|
||||
context.jamClient.SessionRegisterCallback("");
|
||||
context.jamClient.SessionSetAlertCallback("");
|
||||
currentSession = null;
|
||||
currentSessionId = null;
|
||||
}
|
||||
else {
|
||||
deferred = new $.Deferred();
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
return deferred;
|
||||
}
|
||||
|
||||
|
|
@ -118,10 +122,9 @@
|
|||
* Refresh the current session, and participants.
|
||||
*/
|
||||
function refreshCurrentSession() {
|
||||
// XXX use backend instead: https://jamkazam.atlassian.net/browse/VRFS-854
|
||||
logger.debug("SessionModel.refreshCurrentSession()");
|
||||
refreshCurrentSessionRest(function() {
|
||||
refreshCurrentSessionParticipantsRest(sessionChanged);
|
||||
});
|
||||
refreshCurrentSessionRest(sessionChanged);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -139,29 +142,48 @@
|
|||
function sessionChanged() {
|
||||
logger.debug("SessionModel.sessionChanged()");
|
||||
for (var subscriberId in subscribers) {
|
||||
subscribers[subscriberId]();
|
||||
subscribers[subscriberId](currentSession);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCurrentSession(sessionData) {
|
||||
currentSession = sessionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the session data from the REST server, calling
|
||||
* the provided callback when complete.
|
||||
*/
|
||||
function refreshCurrentSessionRest(callback) {
|
||||
var url = "/api/sessions/" + currentSessionId;
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: url,
|
||||
async: false,
|
||||
success: function(response) {
|
||||
sendClientParticipantChanges(currentSession, response);
|
||||
logger.debug("Current Session Refreshed:");
|
||||
logger.debug(response);
|
||||
currentSession = response;
|
||||
callback();
|
||||
},
|
||||
error: ajaxError
|
||||
});
|
||||
if(requestingSessionRefresh) {
|
||||
// if someone asks for a refresh while one is going on, we ask for another to queue up
|
||||
pendingSessionRefresh = true;
|
||||
}
|
||||
else {
|
||||
requestingSessionRefresh = true;
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: url,
|
||||
async: false,
|
||||
success: function(response) {
|
||||
sendClientParticipantChanges(currentSession, response);
|
||||
updateCurrentSession(response);
|
||||
if(callback != null) {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
error: ajaxError,
|
||||
complete: function() {
|
||||
requestingSessionRefresh = false;
|
||||
if(pendingSessionRefresh) {
|
||||
// and when the request is done, if we have a pending, fire t off again
|
||||
pendingSessionRefresh = false;
|
||||
refreshCurrentSessionRest(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -226,36 +248,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that we have user info for all current participants.
|
||||
*/
|
||||
function refreshCurrentSessionParticipantsRest(callback) {
|
||||
var callCount = 0;
|
||||
$.each(participants(), function(index, value) {
|
||||
if (!(this.user.id in users)) {
|
||||
var userInfoUrl = "/api/users/" + this.user.id;
|
||||
callCount += 1;
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: userInfoUrl,
|
||||
async: false,
|
||||
success: function(user) {
|
||||
callCount -= 1;
|
||||
users[user.id] = user;
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
callCount -= 1;
|
||||
logger.error('Error getting user info from ' + userInfoUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!(callback)) {
|
||||
return;
|
||||
}
|
||||
context.JK.joinCalls(
|
||||
function() { return callCount === 0; }, callback, 10);
|
||||
}
|
||||
|
||||
function participantForClientId(clientId) {
|
||||
var foundParticipant = null;
|
||||
|
|
@ -269,7 +261,7 @@
|
|||
}
|
||||
|
||||
function addTrack(sessionId, data) {
|
||||
logger.debug("track data = " + JSON.stringify(data));
|
||||
logger.debug("updating tracks on the server %o", data);
|
||||
var url = "/api/sessions/" + sessionId + "/tracks";
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
|
|
@ -281,9 +273,8 @@
|
|||
processData:false,
|
||||
success: function(response) {
|
||||
// save to the backend
|
||||
context.jamClient.TrackSaveAssignments();
|
||||
logger.debug("Successfully added track (" + JSON.stringify(data) + ")");
|
||||
refreshCurrentSession();
|
||||
logger.debug("successfully updated tracks on the server");
|
||||
//refreshCurrentSession();
|
||||
},
|
||||
error: ajaxError
|
||||
});
|
||||
|
|
@ -307,7 +298,13 @@
|
|||
}
|
||||
|
||||
function deleteTrack(sessionId, trackId) {
|
||||
|
||||
if (trackId) {
|
||||
|
||||
client.TrackSetCount(1);
|
||||
client.TrackSaveAssignments();
|
||||
|
||||
/**
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
url: "/api/sessions/" + sessionId + "/tracks/" + trackId,
|
||||
|
|
@ -326,6 +323,7 @@
|
|||
logger.error("Error deleting track " + trackId);
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -428,6 +426,27 @@
|
|||
logger.error("Unexpected ajax error: " + textStatus);
|
||||
}
|
||||
|
||||
// returns a deferred object
|
||||
function findUserBy(finder) {
|
||||
if(finder.clientId) {
|
||||
var foundParticipant = null;
|
||||
$.each(participants(), function(index, participant) {
|
||||
if(participant.client_id == finder.clientId) {
|
||||
foundParticipant = participant;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if(foundParticipant) {
|
||||
return $.Deferred().resolve(foundParticipant.user).promise();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: find it via some REST API if not found?
|
||||
return $.Deferred().reject().promise();
|
||||
}
|
||||
|
||||
// Public interface
|
||||
this.id = id;
|
||||
this.participants = participants;
|
||||
|
|
@ -440,6 +459,8 @@
|
|||
this.updateTrack = updateTrack;
|
||||
this.deleteTrack = deleteTrack;
|
||||
this.onWebsocketDisconnected = onWebsocketDisconnected;
|
||||
this.recordingModel = recordingModel;
|
||||
this.findUserBy = findUserBy;
|
||||
this.getCurrentSession = function() {
|
||||
return currentSession;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -517,12 +517,21 @@
|
|||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.MUSICIAN_SESSION_DEPART, function(header, payload) {
|
||||
logger.debug("Handling MUSICIAN_SESSION_DEPART msg " + JSON.stringify(payload));
|
||||
|
||||
// display notification
|
||||
app.notify({
|
||||
"title": "Musician Left Session",
|
||||
"text": payload.username + " has left the session.",
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
});
|
||||
if(payload.recordingId && context.JK.CurrentSessionModel.recordingModel.isRecording(payload.recordingId)) {
|
||||
context.JK.CurrentSessionModel.recordingModel.onServerStopRecording(payload.recordingId);
|
||||
/**app.notify({
|
||||
"title": "Recording Stopped",
|
||||
"text": payload.username + " has left the session.",
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
}); */
|
||||
}
|
||||
else {
|
||||
app.notify({
|
||||
"title": "Musician Left Session",
|
||||
"text": payload.username + " has left the session.",
|
||||
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,21 @@
|
|||
// take all necessary arguments to complete its work.
|
||||
context.JK.TrackHelpers = {
|
||||
|
||||
getTracks: function(jamClient, groupId) {
|
||||
var tracks = [];
|
||||
var trackIds = jamClient.SessionGetIDs();
|
||||
var allTracks = jamClient.SessionGetControlState(trackIds);
|
||||
|
||||
// get client's tracks
|
||||
for (var i=0; i < allTracks.length; i++) {
|
||||
if (allTracks[i].group_id === groupId) {
|
||||
tracks.push(allTracks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return tracks;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function resolves which tracks to configure for a user
|
||||
* when creating or joining a session. By default, tracks are pulled
|
||||
|
|
@ -22,80 +37,25 @@
|
|||
*/
|
||||
getUserTracks: function(jamClient) {
|
||||
var trackIds = jamClient.SessionGetIDs();
|
||||
var allTracks = jamClient.SessionGetControlState(trackIds);
|
||||
var localMusicTracks = [];
|
||||
var i;
|
||||
var instruments = [];
|
||||
var localTrackExists = false;
|
||||
|
||||
// get client's tracks
|
||||
for (i=0; i < allTracks.length; i++) {
|
||||
if (allTracks[i].group_id === 2) {
|
||||
localMusicTracks.push(allTracks[i]);
|
||||
|
||||
console.log("allTracks[" + i + "].instrument_id=" + allTracks[i].instrument_id);
|
||||
// check if local track config exists
|
||||
if (allTracks[i].instrument_id !== 0) {
|
||||
localTrackExists = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
localMusicTracks = context.JK.TrackHelpers.getTracks(jamClient, 2);
|
||||
|
||||
var trackObjects = [];
|
||||
|
||||
console.log("localTrackExists=" + localTrackExists);
|
||||
|
||||
// get most proficient instrument from API if no local track config exists
|
||||
if (!localTrackExists) {
|
||||
if (context.JK.userMe.instruments && context.JK.userMe.instruments.length > 0) {
|
||||
var track = {
|
||||
instrument_id: context.JK.userMe.instruments[0].instrument_id,
|
||||
sound: "stereo"
|
||||
};
|
||||
trackObjects.push(track);
|
||||
|
||||
var desc = context.JK.userMe.instruments[0].description;
|
||||
jamClient.TrackSetInstrument(1, context.JK.server_to_client_instrument_map[desc]);
|
||||
jamClient.TrackSaveAssignments();
|
||||
}
|
||||
}
|
||||
// use all tracks previously configured
|
||||
else {
|
||||
console.log("localMusicTracks.length=" + localMusicTracks.length);
|
||||
for (i=0; i < localMusicTracks.length; i++) {
|
||||
var track = {};
|
||||
var instrument_description = '';
|
||||
console.log("localMusicTracks[" + i + "].instrument_id=" + localMusicTracks[i].instrument_id);
|
||||
|
||||
// no instruments configured
|
||||
if (localMusicTracks[i].instrument_id === 0) {
|
||||
if (context.JK.userMe.instruments && context.JK.userMe.instruments.length > 0) {
|
||||
track.instrument_id = context.JK.userMe.instruments[0].instrument_id;
|
||||
}
|
||||
else {
|
||||
track.instrument_id = context.JK.client_to_server_instrument_map[250].server_id;
|
||||
}
|
||||
}
|
||||
// instruments are configured
|
||||
else {
|
||||
if (context.JK.client_to_server_instrument_map[localMusicTracks[i].instrument_id]) {
|
||||
track.instrument_id = context.JK.client_to_server_instrument_map[localMusicTracks[i].instrument_id].server_id;
|
||||
}
|
||||
// fall back to Other
|
||||
else {
|
||||
track.instrument_id = context.JK.client_to_server_instrument_map[250].server_id;
|
||||
jamClient.TrackSetInstrument(i+1, 250);
|
||||
jamClient.TrackSaveAssignments();
|
||||
}
|
||||
}
|
||||
if (localMusicTracks[i].stereo) {
|
||||
track.sound = "stereo";
|
||||
}
|
||||
else {
|
||||
track.sound = "mono";
|
||||
}
|
||||
trackObjects.push(track);
|
||||
for (i=0; i < localMusicTracks.length; i++) {
|
||||
var track = {};
|
||||
track.client_track_id = localMusicTracks[i].id;
|
||||
track.instrument_id = context.JK.client_to_server_instrument_map[localMusicTracks[i].instrument_id].server_id;
|
||||
if (localMusicTracks[i].stereo) {
|
||||
track.sound = "stereo";
|
||||
}
|
||||
else {
|
||||
track.sound = "mono";
|
||||
}
|
||||
trackObjects.push(track);
|
||||
}
|
||||
return trackObjects;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,17 @@
|
|||
}
|
||||
};
|
||||
|
||||
// http://stackoverflow.com/a/8809472/834644
|
||||
context.JK.generateUUID = function(){
|
||||
var d = new Date().getTime();
|
||||
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = (d + Math.random()*16)%16 | 0;
|
||||
d = Math.floor(d/16);
|
||||
return (c=='x' ? r : (r&0x7|0x8)).toString(16);
|
||||
});
|
||||
return uuid;
|
||||
};
|
||||
|
||||
// Build up two maps of images, for each instrument id.
|
||||
// This map is a simple base map of instrument id to the basic image name.
|
||||
// Below, a loop goes through this and builds two size-specific maps.
|
||||
|
|
@ -209,6 +220,19 @@
|
|||
return count;
|
||||
};
|
||||
|
||||
/*
|
||||
* Get the keys of a dictionary as an array (same as Object.keys, but works in all browsers)
|
||||
*/
|
||||
context.JK.dkeys = function(d) {
|
||||
var keys = []
|
||||
for (var i in d) {
|
||||
if (d.hasOwnProperty(i)) {
|
||||
keys.push(i);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the first error associated with the field.
|
||||
* @param fieldName the name of the field
|
||||
|
|
@ -494,4 +518,18 @@
|
|||
return rhex(a) + rhex(b) + rhex(c) + rhex(d);
|
||||
};
|
||||
|
||||
})(window,jQuery);
|
||||
/** validates that no changes are being made to tracks while recording */
|
||||
context.JK.verifyNotRecordingForTrackChange = function(app) {
|
||||
if(context.JK.CurrentSessionModel.recordingModel.isRecording()) {
|
||||
app.notify({
|
||||
title: "Currently Recording",
|
||||
text: "Tracks can not be modified while recording.",
|
||||
icon_url: "/assets/content/icon_alert_big.png"});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
})(window,jQuery);
|
||||
|
|
@ -12,6 +12,7 @@ $ColorLinkHover: #82AEAF;
|
|||
$ColorSidebarText: #a0b9bd;
|
||||
$ColorScreenBackground: lighten($ColorUIBackground, 10%);
|
||||
$ColorTextBoxBackground: #c5c5c5;
|
||||
$ColorRecordingBackground: #471f18;
|
||||
|
||||
$color1: #006AB6; /* mid blue */
|
||||
$color2: #9A9084; /* warm gray */
|
||||
|
|
|
|||
|
|
@ -615,6 +615,14 @@ table.vu td {
|
|||
font-size:18px;
|
||||
}
|
||||
|
||||
.currently-recording {
|
||||
background-color: $ColorRecordingBackground;
|
||||
}
|
||||
|
||||
#recording-timer {
|
||||
margin-left:8px;
|
||||
}
|
||||
|
||||
/* GAIN SLIDER POSITIONS -- TEMPORARY FOR DISPLAY PURPOSES ONLY */
|
||||
.pos0 {
|
||||
bottom:0px;
|
||||
|
|
|
|||
|
|
@ -141,11 +141,24 @@ class ApiMusicSessionsController < ApiController
|
|||
end
|
||||
end
|
||||
|
||||
def track_sync
|
||||
@tracks = Track.sync(params[:client_id], params[:tracks])
|
||||
|
||||
unless @tracks.kind_of? Array
|
||||
# we have to do this because api_session_detail_url will fail with a bad @music_session
|
||||
response.status = :unprocessable_entity
|
||||
respond_with @tracks
|
||||
else
|
||||
respond_with @tracks, responder: ApiResponder
|
||||
end
|
||||
end
|
||||
|
||||
def track_create
|
||||
@track = Track.save(nil,
|
||||
params[:connection_id],
|
||||
params[:instrument_id],
|
||||
params[:sound])
|
||||
params[:sound],
|
||||
params[:client_track_id])
|
||||
|
||||
respond_with @track, responder: ApiResponder, :status => 201, :location => api_session_track_detail_url(@track.connection.music_session, @track)
|
||||
end
|
||||
|
|
@ -155,7 +168,8 @@ class ApiMusicSessionsController < ApiController
|
|||
@track = Track.save(params[:track_id],
|
||||
nil,
|
||||
params[:instrument_id],
|
||||
params[:sound])
|
||||
params[:sound],
|
||||
params[:client_track_id])
|
||||
|
||||
respond_with @track, responder: ApiResponder, :status => 200
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
class ApiRecordingsController < ApiController
|
||||
|
||||
before_filter :api_signed_in_user
|
||||
before_filter :look_up_recording, :only => [ :stop, :claim ]
|
||||
before_filter :look_up_recording, :only => [ :show, :stop, :claim, :keep ]
|
||||
before_filter :parse_filename, :only => [ :upload_next_part, :upload_sign, :upload_part_complete, :upload_complete ]
|
||||
|
||||
respond_to :json
|
||||
|
|
@ -15,37 +15,56 @@ class ApiRecordingsController < ApiController
|
|||
end
|
||||
end
|
||||
|
||||
def show
|
||||
|
||||
end
|
||||
|
||||
def start
|
||||
begin
|
||||
Recording.start(params[:music_session_id], current_user)
|
||||
respond_with responder: ApiResponder, :status => 204
|
||||
rescue
|
||||
render :json => { :message => "recording could not be started" }, :status => 403
|
||||
music_session = MusicSession.find(params[:music_session_id])
|
||||
|
||||
unless music_session.users.exists?(current_user)
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
||||
end
|
||||
|
||||
@recording = Recording.start(music_session, current_user)
|
||||
|
||||
if @recording.errors.any?
|
||||
response.status = :unprocessable_entity
|
||||
respond_with @recording
|
||||
else
|
||||
respond_with @recording, responder: ApiResponder, :location => api_recordings_detail_url(@recording)
|
||||
end
|
||||
end
|
||||
|
||||
def stop
|
||||
begin
|
||||
if @recording.owner_id != current_user.id
|
||||
render :json => { :message => "recording not found" }, :status => 404
|
||||
end
|
||||
@recording.stop
|
||||
respond_with responder: ApiResponder, :status => 204
|
||||
rescue
|
||||
render :json => { :message => "recording could not be stopped" }, :status => 403
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def claim
|
||||
begin
|
||||
claimed_recording = @recording.claim(current_user, params[:name], Genre.find(params[:genre_id]), params[:is_public], params[:is_downloadable])
|
||||
render :json => { :claimed_recording_id => claimed_recording.id }, :status => 200
|
||||
rescue
|
||||
render :json => { :message => "recording could not be claimed" }, :status => 403
|
||||
unless @recording.users.exists?(current_user)
|
||||
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
||||
end
|
||||
|
||||
@recording.stop
|
||||
|
||||
if @recording.errors.any?
|
||||
response.status = :unprocessable_entity
|
||||
respond_with @recording
|
||||
else
|
||||
respond_with @recording, responder: ApiResponder, :location => api_recordings_detail_url(@recording)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# keep will kick off a mix, as well as create a claimed recording for the creator
|
||||
def claim
|
||||
claim = @recording.claim(current_user, params[:name], Genre.find(params[:genre_id]), params[:is_public], params[:is_downloadable])
|
||||
claim.save
|
||||
|
||||
if claim.errors.any?
|
||||
response.status = :unprocessable_entity
|
||||
respond_with claim
|
||||
else
|
||||
respond_with claim, responder: ApiResponder, :location => api_session_detail_url(claim)
|
||||
end
|
||||
end
|
||||
|
||||
def upload_next_part
|
||||
if @recorded_track.next_part_to_upload == 0
|
||||
if (!params[:length] || !params[:md5])
|
||||
|
|
@ -85,10 +104,7 @@ class ApiRecordingsController < ApiController
|
|||
end
|
||||
|
||||
def look_up_recording
|
||||
@recording = Recording.find(params[id])
|
||||
if @recording.nil?
|
||||
render :json => { :message => "recording not found" }, :status => 404
|
||||
end
|
||||
@recording = Recording.find(params[:id])
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ child(:recorded_tracks => :recorded_tracks) {
|
|||
attributes :id, :description
|
||||
}
|
||||
child(:user => :user) {
|
||||
attributes :id, :email, :first_name, :last_name, :city, :state, :country, :photo_url
|
||||
attributes :id, :first_name, :last_name, :city, :state, :country, :photo_url
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@ node :genres do |item|
|
|||
item.genres.map(&:description)
|
||||
end
|
||||
|
||||
if :is_recording?
|
||||
node do |music_session|
|
||||
{ :recording => partial("api_recordings/show", :object => music_session.recording) }
|
||||
end
|
||||
end
|
||||
|
||||
child(:connections => :participants) {
|
||||
collection @music_sessions, :object_root => false
|
||||
attributes :ip_address, :client_id
|
||||
|
|
@ -15,7 +21,7 @@ child(:connections => :participants) {
|
|||
end
|
||||
|
||||
child(:tracks => :tracks) {
|
||||
attributes :id, :connection_id, :instrument_id, :sound
|
||||
attributes :id, :connection_id, :instrument_id, :sound, :client_track_id
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
object @track
|
||||
|
||||
attributes :id, :connection_id, :instrument_id, :sound
|
||||
attributes :id, :connection_id, :instrument_id, :sound, :client_track_id
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
object @recording
|
||||
|
||||
attributes :id, :band, :created_at, :duration
|
||||
|
||||
child(:recorded_tracks => :recorded_tracks) {
|
||||
attributes :id, :client_id, :track_id, :user_id, :fully_uploaded, :url
|
||||
child(:instrument => :instrument) {
|
||||
attributes :id, :description
|
||||
}
|
||||
child(:user => :user) {
|
||||
attributes :id, :first_name, :last_name, :city, :state, :country, :photo_url
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object @recording
|
||||
|
||||
extends "api_recordings/show"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object @recording
|
||||
|
||||
extends "api_recordings/show"
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<!-- Invitation Dialog -->
|
||||
<div class="dialog recordingFinished-overlay ftue-overlay tall" layout="dialog" layout-id="recordingFinished" id="recording-finished-dialog">
|
||||
|
||||
<div class="content-head">
|
||||
<%= image_tag "content/recordbutton-off.png", {:height => 20, :width => 20, :class => 'content-icon'} %>
|
||||
<h1>Recording Finished</h1>
|
||||
</div>
|
||||
|
||||
<div class="dialog-inner">
|
||||
Fill out the fields below and click the "SAVE" button to save this recording to your library. If you do not want to keep the recording, click the "DISCARD" button.
|
||||
</div>
|
||||
|
||||
<a href="#" class="button-grey" layout-action="close">DISCARD</a><br>
|
||||
</div>
|
||||
|
|
@ -83,9 +83,9 @@
|
|||
</p>
|
||||
</div>
|
||||
<br clear="all" />
|
||||
<div class="recording">
|
||||
<div class="recording" id="recording-start-stop">
|
||||
<a>
|
||||
<%= image_tag "content/recordbutton-off.png", {:width => 20, :height => 20, :align => "absmiddle"} %> Make a Recording
|
||||
<%= image_tag "content/recordbutton-off.png", {:width => 20, :height => 20, :align => "absmiddle"} %> <span id="recording-status">Make a Recording</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
<%= render "account_audio_profile" %>
|
||||
<%= render "invitationDialog" %>
|
||||
<%= render "whatsNextDialog" %>
|
||||
<%= render "recordingFinishedDialog" %>
|
||||
<%= render "notify" %>
|
||||
<%= render "client_update" %>
|
||||
<%= render "banner" %>
|
||||
|
|
@ -53,11 +54,7 @@
|
|||
<% end %>
|
||||
if (console) { console.debug("websocket_gateway_uri:" + JK.websocket_gateway_uri); }
|
||||
|
||||
// If no jamClient (when not running in native client)
|
||||
// create a fake one.
|
||||
if (!(window.jamClient)) {
|
||||
window.jamClient = new JK.FakeJamClient();
|
||||
}
|
||||
|
||||
// If no trackVolumeObject (when not running in native client)
|
||||
// create a fake one.
|
||||
if (!(window.trackVolumeObject)) {
|
||||
|
|
@ -170,11 +167,19 @@
|
|||
JK.hideCurtain(300);
|
||||
}
|
||||
|
||||
JK.app = JK.JamKazam();
|
||||
|
||||
// If no jamClient (when not running in native client)
|
||||
// create a fake one.
|
||||
if (!(window.jamClient)) {
|
||||
var p2pMessageFactory = new JK.FakeJamClientMessages();
|
||||
window.jamClient = new JK.FakeJamClient(JK.app, p2pMessageFactory);
|
||||
window.jamClient.SetFakeRecordingImpl(new JK.FakeJamClientRecordings(JK.app, jamClient, p2pMessageFactory));
|
||||
}
|
||||
|
||||
// Let's get things rolling...
|
||||
if (JK.currentUserId) {
|
||||
|
||||
JK.app = JK.JamKazam();
|
||||
|
||||
// do a client update early check upon initialization
|
||||
var clientUpdate = new JK.ClientUpdate(JK.app)
|
||||
clientUpdate.initialize().check()
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ if defined?(Bundler)
|
|||
# filepicker app configured to use S3 bucket jamkazam-dev
|
||||
config.filepicker_rails.api_key = "Asx4wh6GSlmpAAzoM0Cunz"
|
||||
config.filepicker_upload_dir = 'avatars'
|
||||
config.fp_secret = 'YSES4ABIMJCWDFSLCFJUGEBKSE'
|
||||
config.fp_secret = 'FTDL4TYDENBWZKK3UZCFIQWXS4'
|
||||
|
||||
config.recaptcha_enable = false
|
||||
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ SampleApp::Application.routes.draw do
|
|||
|
||||
# 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
|
||||
match '/sessions/:id/tracks' => 'api_music_sessions#track_index', :via => :get
|
||||
match '/sessions/:id/tracks/:track_id' => 'api_music_sessions#track_update', :via => :post
|
||||
match '/sessions/:id/tracks/:track_id' => 'api_music_sessions#track_show', :via => :get, :as => 'api_session_track_detail'
|
||||
|
|
@ -258,10 +259,12 @@ SampleApp::Application.routes.draw do
|
|||
match '/isps' => 'api_maxmind_requests#isps', :via => :get
|
||||
|
||||
# Recordings
|
||||
match '/recordings/list' => 'api_recordings#list', :via => :get
|
||||
match '/recordings/start' => 'api_recordings#start', :via => :post
|
||||
match '/recordings/:id/stop' => 'api_recordings#stop', :via => :put
|
||||
match '/recordings/:id/claim' => 'api_recordings#claim', :via => :post
|
||||
|
||||
match '/recordings/list' => 'api_recordings#list', :via => :get, :as => 'api_recordings_list'
|
||||
match '/recordings/start' => 'api_recordings#start', :via => :post, :as => 'api_recordings_start'
|
||||
match '/recordings/:id' => 'api_recordings#show', :via => :get, :as => 'api_recordings_detail'
|
||||
match '/recordings/:id/stop' => 'api_recordings#stop', :via => :post, :as => 'api_recordings_stop'
|
||||
match '/recordings/:id/claim' => 'api_recordings#claim', :via => :post, :as => 'api_recordings_claim'
|
||||
match '/recordings/upload_next_part' => 'api_recordings#upload_next_part', :via => :get
|
||||
match '/recordings/upload_sign' => 'api_recordings#upload_sign', :via => :get
|
||||
match '/recordings/upload_part_complete' => 'api_recordings#upload_part_complete', :via => :put
|
||||
|
|
|
|||
|
|
@ -125,13 +125,9 @@ MusicSessionManager < BaseManager
|
|||
end
|
||||
|
||||
ConnectionManager.new.leave_music_session(user, connection, music_session) do
|
||||
Notification.send_musician_session_depart(music_session, connection.client_id, user)
|
||||
end
|
||||
|
||||
unless music_session.nil?
|
||||
# send out notification to queue to the rest of the session
|
||||
# TODO: we should rename the notification to music_session_participants_change or something
|
||||
# TODO: also this isn't necessarily a user leaving; it's a client leaving
|
||||
recording = music_session.stop_recording # stop any ongoing recording, if there is one
|
||||
recordingId = recording.id unless recording.nil?
|
||||
Notification.send_musician_session_depart(music_session, connection.client_id, user, recordingId)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ describe ApiClaimedRecordingsController do
|
|||
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
|
||||
@music_session.connections << @connection
|
||||
@music_session.save
|
||||
@recording = Recording.start(@music_session.id, @user)
|
||||
@recording = Recording.start(@music_session, @user)
|
||||
@recording.stop
|
||||
@recording.reload
|
||||
@genre = FactoryGirl.create(:genre)
|
||||
@recording.claim(@user, "name", @genre, true, true)
|
||||
@recording.claim(@user, "name", "description", @genre, true, true)
|
||||
@recording.reload
|
||||
@claimed_recording = @recording.claimed_recordings.first
|
||||
end
|
||||
|
|
@ -26,7 +26,7 @@ describe ApiClaimedRecordingsController do
|
|||
it "should show the right thing when one recording just finished" do
|
||||
controller.current_user = @user
|
||||
get :show, :id => @claimed_recording.id
|
||||
response.should be_success
|
||||
response.should be_success
|
||||
json = JSON.parse(response.body)
|
||||
json.should_not be_nil
|
||||
json["id"].should == @claimed_recording.id
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ApiRecordingsController do
|
||||
render_views
|
||||
|
||||
|
||||
before(:each) do
|
||||
@user = FactoryGirl.create(:user)
|
||||
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
|
||||
@music_session = FactoryGirl.create(:music_session, :creator => @user, :musician_access => true)
|
||||
@connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session)
|
||||
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
|
||||
controller.current_user = @user
|
||||
end
|
||||
|
||||
describe "start" do
|
||||
it "should work" do
|
||||
post :start, { :format => 'json', :music_session_id => @music_session.id }
|
||||
response.should be_success
|
||||
response_body = JSON.parse(response.body)
|
||||
response_body['id'].should_not be_nil
|
||||
recording = Recording.find(response_body['id'])
|
||||
end
|
||||
|
||||
it "should not allow multiple starts" do
|
||||
post :start, { :format => 'json', :music_session_id => @music_session.id }
|
||||
post :start, { :format => 'json', :music_session_id => @music_session.id }
|
||||
response.status.should == 422
|
||||
response_body = JSON.parse(response.body)
|
||||
response_body["errors"]["music_session"][0].should == ValidationMessages::ALREADY_BEING_RECORDED
|
||||
end
|
||||
|
||||
it "should not allow start by somebody not in the music session" do
|
||||
user2 = FactoryGirl.create(:user)
|
||||
controller.current_user = user2
|
||||
post :start, { :format => 'json', :music_session_id => @music_session.id }
|
||||
response.status.should == 403
|
||||
end
|
||||
end
|
||||
|
||||
describe "get" do
|
||||
it "should work" do
|
||||
post :start, { :format => 'json', :music_session_id => @music_session.id }
|
||||
response.should be_success
|
||||
response_body = JSON.parse(response.body)
|
||||
response_body['id'].should_not be_nil
|
||||
recordingId = response_body['id']
|
||||
get :show, {:format => 'json', :id => recordingId}
|
||||
response.should be_success
|
||||
response_body = JSON.parse(response.body)
|
||||
response_body['id'].should == recordingId
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "stop" do
|
||||
it "should work" do
|
||||
post :start, { :format => 'json', :music_session_id => @music_session.id }
|
||||
response_body = JSON.parse(response.body)
|
||||
recording = Recording.find(response_body['id'])
|
||||
post :stop, { :format => 'json', :id => recording.id }
|
||||
response.should be_success
|
||||
response_body = JSON.parse(response.body)
|
||||
response_body['id'].should_not be_nil
|
||||
Recording.find(response_body['id']).id.should == recording.id
|
||||
end
|
||||
|
||||
it "should not allow stop on a session not being recorded" do
|
||||
post :start, { :format => 'json', :music_session_id => @music_session.id }
|
||||
response_body = JSON.parse(response.body)
|
||||
recording = Recording.find(response_body['id'])
|
||||
post :stop, { :format => 'json', :id => recording.id }
|
||||
post :stop, { :format => 'json', :id => recording.id }
|
||||
response.status.should == 422
|
||||
response_body = JSON.parse(response.body)
|
||||
end
|
||||
|
||||
it "should not allow stop on a session requested by a different member" do
|
||||
|
||||
post :start, { :format => 'json', :music_session_id => @music_session.id }
|
||||
response_body = JSON.parse(response.body)
|
||||
recording = Recording.find(response_body['id'])
|
||||
user2 = FactoryGirl.create(:user)
|
||||
controller.current_user = user2
|
||||
post :stop, { :format => 'json', :id => recording.id }
|
||||
response.status.should == 403
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -120,6 +120,7 @@ FactoryGirl.define do
|
|||
|
||||
factory :track, :class => JamRuby::Track do
|
||||
sound "mono"
|
||||
sequence(:client_track_id) { |n| "client_track_id_seq_#{n}"}
|
||||
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,230 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe "Find Session", :js => true, :type => :feature, :capybara_feature => true, :slow => true do
|
||||
|
||||
subject { page }
|
||||
|
||||
before(:all) do
|
||||
Capybara.javascript_driver = :poltergeist
|
||||
Capybara.current_driver = Capybara.javascript_driver
|
||||
Capybara.default_wait_time = 30 # these tests are SLOOOOOW
|
||||
end
|
||||
|
||||
let(:creator) { FactoryGirl.create(:user) }
|
||||
let(:joiner1) { FactoryGirl.create(:user) }
|
||||
|
||||
before(:each) do
|
||||
MusicSession.delete_all
|
||||
end
|
||||
|
||||
# creates a recording, and stops it, and confirms the 'Finished Recording' dialog shows for both
|
||||
it "creator start/stop" do
|
||||
create_join_session(creator, [joiner1])
|
||||
|
||||
in_client(creator) do
|
||||
find('#recording-start-stop').trigger(:click)
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#notification').should have_content 'started a recording'
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(creator) do
|
||||
find('#recording-start-stop').trigger(:click)
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
should have_selector('h1', 'Recording Finished')
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
should have_selector('h1', 'Recording Finished')
|
||||
end
|
||||
end
|
||||
|
||||
# confirms that anyone can start/stop a recording
|
||||
it "creator starts and other stops" do
|
||||
create_join_session(creator, [joiner1])
|
||||
|
||||
in_client(creator) do
|
||||
find('#recording-start-stop').trigger(:click)
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#notification').should have_content 'started a recording'
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#recording-start-stop').trigger(:click)
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
should have_selector('h1', 'Recording Finished')
|
||||
end
|
||||
|
||||
in_client(creator) do
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
should have_selector('h1', 'Recording Finished')
|
||||
end
|
||||
end
|
||||
|
||||
# confirms that a formal leave (by hitting the 'Leave' button) will result in a good recording
|
||||
it "creator starts and then leaves" do
|
||||
create_join_session(creator, [joiner1])
|
||||
|
||||
in_client(creator) do
|
||||
find('#recording-start-stop').trigger(:click)
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#notification').should have_content 'started a recording'
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(creator) do
|
||||
find('#session-leave').trigger(:click)
|
||||
expect(page).to have_selector('h2', text: 'feed')
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
should have_selector('h1', 'Recording Finished')
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
should have_selector('h1', 'Recording Finished')
|
||||
end
|
||||
end
|
||||
|
||||
# confirms that if someone leaves 'ugly' (without calling 'Leave' REST API), that the recording is junked
|
||||
it "creator starts and then abruptly leave" do
|
||||
create_join_session(creator, [joiner1])
|
||||
|
||||
in_client(creator) do
|
||||
find('#recording-start-stop').trigger(:click)
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#notification').should have_content 'started a recording'
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(creator) do
|
||||
visit "http://www.google.com" # kills websocket, looking like an abrupt leave
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#notification').should have_content 'Recording Deleted'
|
||||
find('#notification').should have_content 'someone in the session has disconnected'
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
end
|
||||
end
|
||||
|
||||
it "creator starts/stops, with 3 total participants" do
|
||||
joiner2 = FactoryGirl.create(:user)
|
||||
|
||||
create_join_session(creator, [joiner1, joiner2])
|
||||
|
||||
in_client(creator) do
|
||||
find('#recording-start-stop').trigger(:click)
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(joiner2) do
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(creator) do
|
||||
find('#recording-start-stop').trigger(:click)
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
should have_selector('h1', 'Recording Finished')
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
should have_selector('h1', 'Recording Finished')
|
||||
end
|
||||
|
||||
in_client(joiner2) do
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
should have_selector('h1', 'Recording Finished')
|
||||
end
|
||||
end
|
||||
|
||||
it "creator starts with session leave to stop, with 3 total participants" do
|
||||
joiner2 = FactoryGirl.create(:user)
|
||||
create_join_session(creator, [joiner1, joiner2])
|
||||
|
||||
in_client(creator) do
|
||||
find('#recording-start-stop').trigger(:click)
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(joiner2) do
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(creator) do
|
||||
find('#session-leave').trigger(:click)
|
||||
expect(page).to have_selector('h2', text: 'feed')
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
should have_selector('h1', 'Recording Finished')
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
should have_selector('h1', 'Recording Finished')
|
||||
end
|
||||
|
||||
in_client(joiner2) do
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
should have_selector('h1', 'Recording Finished')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# confirms that if someone leaves 'ugly' (without calling 'Leave' REST API), that the recording is junked with 3 participants
|
||||
it "creator starts and then abruptly leave with 3 participants" do
|
||||
joiner2 = FactoryGirl.create(:user)
|
||||
create_join_session(creator, [joiner1, joiner2])
|
||||
|
||||
in_client(creator) do
|
||||
find('#recording-start-stop').trigger(:click)
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(joiner2) do
|
||||
find('#recording-status').should have_content 'Stop Recording'
|
||||
end
|
||||
|
||||
in_client(creator) do
|
||||
visit "http://www.google.com" # kills websocket, looking like an abrupt leave
|
||||
end
|
||||
|
||||
in_client(joiner1) do
|
||||
find('#notification').should have_content 'Recording Deleted'
|
||||
find('#notification').should have_content 'someone in the session has disconnected'
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
end
|
||||
|
||||
in_client(joiner2) do
|
||||
find('#notification').should have_content 'Recording Deleted'
|
||||
find('#notification').should have_content 'someone in the session has disconnected'
|
||||
find('#recording-status').should have_content 'Make a Recording'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(function(context, $) {
|
||||
v(function(context, $) {
|
||||
describe("Callbacks", function() {
|
||||
describe("makeStatic", function() {
|
||||
it("should create static function which invokes instance function", function() {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
describe("faderHelpers tests", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
JKTestUtils.loadFixtures('/base/app/views/clients/_faders.html.erb');
|
||||
JKTestUtils.loadFixtures('/base/spec/javascripts/fixtures/faders.htm');
|
||||
JKTestUtils.loadFixtures('/app/views/clients/_faders.html.erb');
|
||||
JKTestUtils.loadFixtures('/spec/javascripts/fixtures/faders.htm');
|
||||
});
|
||||
|
||||
describe("renderVU", function() {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
beforeEach(function() {
|
||||
fss = null;
|
||||
// Use the actual screen markup
|
||||
JKTestUtils.loadFixtures('/base/app/views/clients/_findSession.html.erb');
|
||||
JKTestUtils.loadFixtures('/app/views/clients/_findSession.html.erb');
|
||||
spyOn(appFake, 'notify');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
describe("jquery.formToObject tests", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
JKTestUtils.loadFixtures('/base/spec/javascripts/fixtures/formToObject.htm');
|
||||
JKTestUtils.loadFixtures('/spec/javascripts/fixtures/formToObject.htm');
|
||||
});
|
||||
|
||||
describe("Top level", function() {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,75 @@
|
|||
(function(context, $) {
|
||||
|
||||
describe("RecordingModel", function() {
|
||||
var recordingModel = null;
|
||||
var sessionModel = null;
|
||||
var app = null;
|
||||
var rest = null;
|
||||
var jamClient = null;
|
||||
var validRecordingData = null;
|
||||
beforeEach(function() {
|
||||
app = { };
|
||||
sessionModel = { id: null };
|
||||
rest = { startRecording: null, stopRecording: null};
|
||||
jamClient = { StartRecording: null, StopRecording: null};
|
||||
recordingModel = new context.JK.RecordingModel(app, sessionModel, rest, jamClient);
|
||||
validRecordingData = {
|
||||
id: '1',
|
||||
recorded_tracks: [
|
||||
{ id: '1', track_id: '1', user_id: '1', 'client_id':'1' }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
it("constructs", function() {
|
||||
|
||||
});
|
||||
|
||||
it("allows start recording", function() {
|
||||
|
||||
spyOn(sessionModel, 'id').andReturn('1');
|
||||
spyOn(rest, 'startRecording').andCallFake(function (req) {
|
||||
return $.Deferred().resolve(validRecordingData).promise();
|
||||
});
|
||||
spyOn(jamClient, 'StartRecording').andCallFake(function(recordingId, tracks) {
|
||||
eval(context.JK.HandleRecordingStartResult).call(this, recordingId, true);
|
||||
});
|
||||
spyOnEvent($(recordingModel), 'startingRecording');
|
||||
spyOnEvent($(recordingModel), 'startedRecording');
|
||||
|
||||
expect(recordingModel.startRecording()).toBe(true);
|
||||
|
||||
expect('startingRecording').toHaveBeenTriggeredOn($(recordingModel));
|
||||
expect('startedRecording').toHaveBeenTriggeredOn($(recordingModel));
|
||||
});
|
||||
|
||||
it("allows stop recording", function() {
|
||||
|
||||
spyOn(sessionModel, 'id').andReturn('1');
|
||||
spyOn(rest, 'startRecording').andCallFake(function (req) {
|
||||
return $.Deferred().resolve(validRecordingData).promise();
|
||||
});
|
||||
spyOn(rest, 'stopRecording').andCallFake(function (req) {
|
||||
return $.Deferred().resolve(validRecordingData).promise();
|
||||
});
|
||||
spyOn(jamClient, 'StartRecording').andCallFake(function(recordingId, tracks) {
|
||||
eval(context.JK.HandleRecordingStartResult).call(this, recordingId, true);
|
||||
});
|
||||
spyOn(jamClient, 'StopRecording').andCallFake(function(recordingId, tracks) {
|
||||
eval(context.JK.HandleRecordingStopResult).call(this, recordingId, true);
|
||||
});
|
||||
|
||||
spyOnEvent($(recordingModel), 'stoppingRecording');
|
||||
spyOnEvent($(recordingModel), 'stoppedRecording');
|
||||
|
||||
|
||||
expect(recordingModel.startRecording()).toBe(true);
|
||||
expect(recordingModel.stopRecording()).toBe(true);
|
||||
|
||||
expect('stoppingRecording').toHaveBeenTriggeredOn($(recordingModel));
|
||||
expect('stoppedRecording').toHaveBeenTriggeredOn($(recordingModel));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}(window, jQuery));
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
describe("searcher.js tests", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
JKTestUtils.loadFixtures('/base/spec/javascripts/fixtures/searcher.htm');
|
||||
JKTestUtils.loadFixtures('/spec/javascripts/fixtures/searcher.htm');
|
||||
});
|
||||
|
||||
describe("Empty Search", function() {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
describe("vuHelper tests", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
JKTestUtils.loadFixtures('/base/app/views/clients/_vu_meters.html.erb');
|
||||
JKTestUtils.loadFixtures('/base/spec/javascripts/fixtures/vuHelpers.htm');
|
||||
JKTestUtils.loadFixtures('/app/views/clients/_vu_meters.html.erb');
|
||||
JKTestUtils.loadFixtures('/spec/javascripts/fixtures/vuHelpers.htm');
|
||||
});
|
||||
|
||||
describe("renderVU", function() {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ describe MusicSessionManager do
|
|||
@band = FactoryGirl.create(:band)
|
||||
@genre = FactoryGirl.create(:genre)
|
||||
@instrument = FactoryGirl.create(:instrument)
|
||||
@tracks = [{"instrument_id" => @instrument.id, "sound" => "mono"}]
|
||||
@tracks = [{"instrument_id" => @instrument.id, "sound" => "mono", "client_track_id" => "abcd"}]
|
||||
@connection = FactoryGirl.create(:connection, :user => @user)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ describe "Music Session API ", :type => :api do
|
|||
let(:user) { FactoryGirl.create(:user) }
|
||||
|
||||
# defopts are used to setup default options for the session
|
||||
let(:defopts) { { :description => "a session", :fan_chat => true, :fan_access => true, :approval_required => false, :genres => ["classical"], :musician_access => true, :tracks => [{"instrument_id" => "electric guitar", "sound" => "mono"}], :legal_terms => true, :intellectual_property => true} }
|
||||
let(:defopts) { { :description => "a session", :fan_chat => true, :fan_access => true, :approval_required => false, :genres => ["classical"], :musician_access => true, :tracks => [{"instrument_id" => "electric guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}], :legal_terms => true, :intellectual_property => true} }
|
||||
before do
|
||||
#sign_in user
|
||||
MusicSession.delete_all
|
||||
|
|
@ -115,7 +115,7 @@ describe "Music Session API ", :type => :api do
|
|||
|
||||
# create a 2nd track for this session
|
||||
conn_id = updated_track["connection_id"]
|
||||
post "/api/sessions/#{music_session["id"]}/tracks.json", { :connection_id => "#{conn_id}", :instrument_id => "electric guitar", :sound => "mono" }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post "/api/sessions/#{music_session["id"]}/tracks.json", { :connection_id => "#{conn_id}", :instrument_id => "electric guitar", :sound => "mono", :client_track_id => "client_track_guid" }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should == 201
|
||||
|
||||
get "/api/sessions/#{music_session["id"]}/tracks.json", "CONTENT_TYPE" => 'application/json'
|
||||
|
|
@ -239,7 +239,7 @@ describe "Music Session API ", :type => :api do
|
|||
musician["client_id"].should == client.client_id
|
||||
|
||||
login(user2)
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
|
||||
last_response.status.should eql(201)
|
||||
|
||||
|
|
@ -301,7 +301,7 @@ describe "Music Session API ", :type => :api do
|
|||
client = FactoryGirl.create(:connection, :user => user, :ip_address => "1.1.1.1")
|
||||
post '/api/sessions.json', defopts.merge({:client_id => client.client_id, :tracks => nil}).to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(422)
|
||||
JSON.parse(last_response.body)["errors"]["genres"][0].should == Connection::SELECT_AT_LEAST_ONE
|
||||
JSON.parse(last_response.body)["errors"]["genres"][0].should == ValidationMessages::SELECT_AT_LEAST_ONE
|
||||
|
||||
# check that the transaction was rolled back
|
||||
MusicSession.all().length.should == original_count
|
||||
|
|
@ -311,7 +311,7 @@ describe "Music Session API ", :type => :api do
|
|||
original_count = MusicSession.all().length
|
||||
|
||||
client = FactoryGirl.create(:connection, :user => user, :ip_address => "1.1.1.1")
|
||||
post '/api/sessions.json', defopts.merge({:client_id => client.client_id, :tracks => [{"instrument_id" => "mom", "sound" => "mono"}]}).to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post '/api/sessions.json', defopts.merge({:client_id => client.client_id, :tracks => [{"instrument_id" => "mom", "sound" => "mono", "client_track_id" => "client_track_guid"}]}).to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(404)
|
||||
|
||||
# check that the transaction was rolled back
|
||||
|
|
@ -322,7 +322,7 @@ describe "Music Session API ", :type => :api do
|
|||
original_count = MusicSession.all().length
|
||||
|
||||
client = FactoryGirl.create(:connection, :user => user, :ip_address => "1.1.1.1")
|
||||
post '/api/sessions.json', defopts.merge({:client_id => client.client_id, :tracks => [{"instrument_id" => "electric guitar", "sound" => "mom"}]}).to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post '/api/sessions.json', defopts.merge({:client_id => client.client_id, :tracks => [{"instrument_id" => "electric guitar", "sound" => "mom", "client_track_id" => "client_track_guid"}]}).to_json, "CONTENT_TYPE" => 'application/json'
|
||||
|
||||
last_response.status.should eql(422)
|
||||
JSON.parse(last_response.body)["errors"]["tracks"][0].should == "is invalid"
|
||||
|
|
@ -411,10 +411,10 @@ describe "Music Session API ", :type => :api do
|
|||
|
||||
# users are friends, but no invitation... so we shouldn't be able to join as user 2
|
||||
login(user2)
|
||||
post "/api/sessions/#{session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono"}] }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post "/api/sessions/#{session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}] }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(422)
|
||||
join_response = JSON.parse(last_response.body)
|
||||
join_response["errors"]["musician_access"].should == [Connection::INVITE_REQUIRED]
|
||||
join_response["errors"]["musician_access"].should == [ValidationMessages::INVITE_REQUIRED]
|
||||
|
||||
# but let's make sure if we then invite, that we can then join'
|
||||
login(user)
|
||||
|
|
@ -422,7 +422,7 @@ describe "Music Session API ", :type => :api do
|
|||
last_response.status.should eql(201)
|
||||
|
||||
login(user2)
|
||||
post "/api/sessions/#{session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono"}] }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post "/api/sessions/#{session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}] }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
|
||||
last_response.status.should eql(201)
|
||||
end
|
||||
|
|
@ -492,10 +492,10 @@ describe "Music Session API ", :type => :api do
|
|||
client2 = FactoryGirl.create(:connection, :user => user2, :ip_address => "2.2.2.2")
|
||||
|
||||
login(user2)
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(422)
|
||||
rejected_join_attempt = JSON.parse(last_response.body)
|
||||
rejected_join_attempt["errors"]["approval_required"] = [Connection::INVITE_REQUIRED]
|
||||
rejected_join_attempt["errors"]["approval_required"] = [ValidationMessages::INVITE_REQUIRED]
|
||||
|
||||
# now send up a join_request to try and get in
|
||||
login(user2)
|
||||
|
|
@ -514,7 +514,7 @@ describe "Music Session API ", :type => :api do
|
|||
|
||||
# finally, go back to user2 and attempt to join again
|
||||
login(user2)
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(201)
|
||||
end
|
||||
|
||||
|
|
@ -544,7 +544,7 @@ describe "Music Session API ", :type => :api do
|
|||
track["sound"].should == "mono"
|
||||
|
||||
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client.client_id, :as_musician => true, :tracks => [{"instrument_id" => "electric guitar", "sound" => "mono"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client.client_id, :as_musician => true, :tracks => [{"instrument_id" => "electric guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
|
||||
last_response.status.should eql(201)
|
||||
|
||||
|
|
@ -558,6 +558,33 @@ describe "Music Session API ", :type => :api do
|
|||
track["instrument_id"].should == "electric guitar"
|
||||
track["sound"].should == "mono"
|
||||
end
|
||||
|
||||
it "can't join session that's recording" do
|
||||
user = FactoryGirl.create(:user)
|
||||
user2 = FactoryGirl.create(:user)
|
||||
client = FactoryGirl.create(:connection, :user => user)
|
||||
client2 = FactoryGirl.create(:connection, :user => user2)
|
||||
instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
|
||||
track = FactoryGirl.create(:track, :connection => client, :instrument => instrument)
|
||||
track2 = FactoryGirl.create(:track, :connection => client2, :instrument => instrument)
|
||||
|
||||
# 1st user joins
|
||||
login(user)
|
||||
post '/api/sessions.json', defopts.merge({:client_id => client.client_id}).to_json, "CONTENT_TYPE" => 'application/json'
|
||||
location_header = last_response.headers["Location"]
|
||||
get location_header
|
||||
music_session = JSON.parse(last_response.body)
|
||||
|
||||
# start a recording
|
||||
post "/api/recordings/start", {:format => :json, :music_session_id => music_session['id'] }.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(201)
|
||||
|
||||
# user 2 should not be able to join
|
||||
login(user2)
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "electric guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(422)
|
||||
JSON.parse(last_response.body)["errors"]["music_session"][0].should == ValidationMessages::CANT_JOIN_RECORDING_SESSION
|
||||
end
|
||||
end
|
||||
|
||||
it "Finds a single open session" do
|
||||
|
|
@ -607,9 +634,36 @@ describe "Music Session API ", :type => :api do
|
|||
last_response.status.should == 200
|
||||
msuh.reload
|
||||
msuh.rating.should == 0
|
||||
|
||||
end
|
||||
|
||||
it "track sync" do
|
||||
user = FactoryGirl.create(:single_user_session)
|
||||
instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
|
||||
music_session = FactoryGirl.create(:music_session, :creator => user)
|
||||
client = FactoryGirl.create(:connection, :user => user, :music_session => music_session)
|
||||
track = FactoryGirl.create(:track, :connection => client, :instrument => instrument)
|
||||
|
||||
existing_track = {:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id }
|
||||
new_track = {:client_track_id => "client_track_id1", :instrument_id => instrument.id, :sound => 'stereo'}
|
||||
# let's add a new track, and leave the existing one alone
|
||||
tracks = [existing_track, new_track]
|
||||
login(user)
|
||||
|
||||
put "/api/sessions/#{music_session.id}/tracks.json", { :client_id => client.client_id, :tracks => tracks }.to_json, "CONTENT_TYPE" => "application/json"
|
||||
last_response.status.should == 204
|
||||
|
||||
get "/api/sessions/#{music_session.id}/tracks.json", "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should == 200
|
||||
tracks = JSON.parse(last_response.body)
|
||||
tracks.size.should == 2
|
||||
tracks[0]["id"].should == track.id
|
||||
tracks[0]["instrument_id"].should == instrument.id
|
||||
tracks[0]["sound"].should == "mono"
|
||||
tracks[0]["client_track_id"].should == track.client_track_id
|
||||
tracks[1]["instrument_id"].should == instrument.id
|
||||
tracks[1]["sound"].should == "stereo"
|
||||
tracks[1]["client_track_id"].should == "client_track_id1"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ describe "User Progression", :type => :api do
|
|||
|
||||
describe "user progression" do
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:defopts) { { :description => "a session", :fan_chat => true, :fan_access => true, :approval_required => false, :genres => ["classical"], :musician_access => true, :tracks => [{"instrument_id" => "electric guitar", "sound" => "mono"}], :legal_terms => true, :intellectual_property => true} }
|
||||
let(:defopts) { { :description => "a session", :fan_chat => true, :fan_access => true, :approval_required => false, :genres => ["classical"], :musician_access => true, :tracks => [{"instrument_id" => "electric guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}], :legal_terms => true, :intellectual_property => true} }
|
||||
|
||||
before do
|
||||
login(user)
|
||||
|
|
@ -105,11 +105,11 @@ describe "User Progression", :type => :api do
|
|||
music_session = JSON.parse(last_response.body)
|
||||
|
||||
login(user2)
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client2.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(201)
|
||||
|
||||
login(user3)
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client3.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
post "/api/sessions/#{music_session["id"]}/participants.json", { :client_id => client3.client_id, :as_musician => true, :tracks => [{"instrument_id" => "bass guitar", "sound" => "mono", "client_track_id" => "client_track_guid"}]}.to_json, "CONTENT_TYPE" => 'application/json'
|
||||
last_response.status.should eql(201)
|
||||
|
||||
# instrument the created_at of the music_history field to be at the beginning of time, so that we cross the 15 minute threshold of a 'real session
|
||||
|
|
|
|||
|
|
@ -114,6 +114,12 @@ Spork.prefork do
|
|||
if example.metadata[:js]
|
||||
#sleep (ENV['SLEEP_JS'] || 0.2).to_i # necessary though otherwise intermittent failures: http://stackoverflow.com/questions/14265983/upgrading-capybara-from-1-0-1-to-1-1-4-makes-database-cleaner-break-my-specs
|
||||
end
|
||||
|
||||
# dump response.body if an example fails
|
||||
if example.metadata[:type] == :controller && example.exception
|
||||
puts "'#{determine_test_name(example.metadata)}' controller test failed."
|
||||
puts "response.status = #{response.status}, response.body = " + response.body
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
include ApplicationHelper
|
||||
|
||||
def cookie_jar
|
||||
Capybara.current_session.driver.browser.current_session.instance_variable_get(:@rack_mock_session).cookie_jar
|
||||
Capybara.current_session.driver.browser.current_session.instance_variable_get(:@rack_mock_session).cookie_jar
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -69,15 +69,15 @@ def wait_until_user(wait=Capybara.default_wait_time)
|
|||
wait = wait * 10 #(because we sleep .1)
|
||||
|
||||
counter = 0
|
||||
# while page.execute_script("$('.curtain').is(:visible)") == "true"
|
||||
# counter += 1
|
||||
# sleep(0.1)
|
||||
# raise "Waiting for user to populate took longer than #{wait} seconds." if counter >= wait
|
||||
# end
|
||||
# while page.execute_script("$('.curtain').is(:visible)") == "true"
|
||||
# counter += 1
|
||||
# sleep(0.1)
|
||||
# raise "Waiting for user to populate took longer than #{wait} seconds." if counter >= wait
|
||||
# end
|
||||
end
|
||||
|
||||
def wait_until_curtain_gone
|
||||
should have_no_selector('.curtain')
|
||||
should have_no_selector('.curtain')
|
||||
end
|
||||
|
||||
def wait_to_see_my_track
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ module JamWebsockets
|
|||
end
|
||||
|
||||
def add_client(client_id, client_context)
|
||||
@log.debug "adding client #{client_id} to @client_lookup"
|
||||
@client_lookup[client_id] = client_context
|
||||
end
|
||||
|
||||
|
|
@ -172,20 +173,26 @@ module JamWebsockets
|
|||
client_id = routing_key["client.".length..-1]
|
||||
@semaphore.synchronize do
|
||||
client_context = @client_lookup[client_id]
|
||||
client = client_context.client
|
||||
|
||||
msg = Jampb::ClientMessage.parse(msg)
|
||||
if !client_context.nil?
|
||||
|
||||
@log.debug "client-directed message received from #{msg.from} to client #{client_id}"
|
||||
client = client_context.client
|
||||
|
||||
unless client.nil?
|
||||
msg = Jampb::ClientMessage.parse(msg)
|
||||
|
||||
EM.schedule do
|
||||
@log.debug "sending client-directed down websocket to #{client_id}"
|
||||
send_to_client(client, msg)
|
||||
@log.debug "client-directed message received from #{msg.from} to client #{client_id}"
|
||||
|
||||
unless client.nil?
|
||||
|
||||
EM.schedule do
|
||||
@log.debug "sending client-directed down websocket to #{client_id}"
|
||||
send_to_client(client, msg)
|
||||
end
|
||||
else
|
||||
@log.debug "client-directed message unroutable to disconnected client #{client_id}"
|
||||
end
|
||||
else
|
||||
@log.debug "client-directed message unroutable to disconnected client #{client_id}"
|
||||
@log.debug "Can't route message: no client connected with id #{client_id}"
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
|
|
@ -342,7 +349,9 @@ module JamWebsockets
|
|||
Notification.send_friend_update(user_id, false, conn) if count == 0
|
||||
music_session = MusicSession.find_by_id(music_session_id) unless music_session_id.nil?
|
||||
user = User.find_by_id(user_id) unless user_id.nil?
|
||||
Notification.send_musician_session_depart(music_session, cid, user) unless music_session.nil? || user.nil?
|
||||
recording = music_session.stop_recording unless music_session.nil? # stop any ongoing recording, if there is one
|
||||
recordingId = recording.id unless recording.nil?
|
||||
Notification.send_musician_session_depart(music_session, cid, user, recordingId) unless music_session.nil? || user.nil?
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -475,7 +484,11 @@ module JamWebsockets
|
|||
if music_session_id.nil?
|
||||
# if this is a reclaim of a connection, but music_session_id comes back null, then we need to check if this connection was IN a music session before.
|
||||
# if so, then we need to tell the others in the session that this user is now departed
|
||||
Notification.send_musician_session_depart(music_session_upon_reentry, client.client_id, context.user) unless context.nil? || music_session_upon_reentry.nil? || music_session_upon_reentry.destroyed?
|
||||
unless context.nil? || music_session_upon_reentry.nil? || music_session_upon_reentry.destroyed?
|
||||
recording = music_session_upon_reentry.stop_recording
|
||||
recordingId = recording.id unless recording.nil?
|
||||
Notification.send_musician_session_depart(music_session_upon_reentry, client.client_id, context.user, recordingId)
|
||||
end
|
||||
else
|
||||
music_session = MusicSession.find_by_id(music_session_id)
|
||||
Notification.send_musician_session_fresh(music_session, client.client_id, context.user) unless context.nil?
|
||||
|
|
@ -520,25 +533,6 @@ module JamWebsockets
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: deprecated; jam_ruby has routine inspired by this
|
||||
def send_friend_update(user, online, client)
|
||||
@log.debug "sending friend update for user #{user} online = #{online}"
|
||||
|
||||
if !user.nil? && user.friends.exists?
|
||||
@log.debug "user has friends - sending friend updates"
|
||||
|
||||
# create the friend_update message
|
||||
friend_update_msg = @message_factory.friend_update(user.id, online)
|
||||
|
||||
# send the friend_update to each friend that has active connections
|
||||
user.friends.each do |friend|
|
||||
@log.debug "sending friend update message to #{friend}"
|
||||
|
||||
handle_user_directed(friend.id, friend_update_msg, client)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handle_heartbeat(heartbeat, heartbeat_message_id, client)
|
||||
unless context = @clients[client]
|
||||
@log.warn "*** WARNING: unable to find context due to heartbeat from client: #{client.client_id}; calling cleanup"
|
||||
|
|
@ -653,6 +647,10 @@ module JamWebsockets
|
|||
# belong to
|
||||
access_p2p(to_client_id, context.user, client_msg)
|
||||
|
||||
if to_client_id.nil? || to_client_id == 'undefined' # javascript translates to 'undefined' in many cases
|
||||
raise SessionError, "empty client_id specified in peer-to-peer message"
|
||||
end
|
||||
|
||||
# populate routing data
|
||||
client_msg.from = client.client_id
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ module JamWebsockets
|
|||
@log.info "starting server #{host}:#{port} with staleness_time=#{connect_time_stale}; reconnect time = #{connect_time_expire}"
|
||||
|
||||
EventMachine.error_handler{|e|
|
||||
@log.error "unhandled error #{e}"
|
||||
Bugsnag.notify(e)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue