diff --git a/core/src/avm1/globals/sound.rs b/core/src/avm1/globals/sound.rs index 1a7897808cc82..1ea352717fec5 100644 --- a/core/src/avm1/globals/sound.rs +++ b/core/src/avm1/globals/sound.rs @@ -5,7 +5,7 @@ use std::fmt; use std::io::Cursor; use gc_arena::barrier::unlock; -use gc_arena::{Collect, Gc, Mutation, RefLock}; +use gc_arena::{Collect, Gc, Lock, Mutation}; use id3::Tag; use ruffle_macros::istr; @@ -23,32 +23,90 @@ use crate::display_object::{DisplayObject, SoundTransform, TDisplayObject}; use crate::string::AvmString; use crate::{avm_warn, avm1_stub}; -#[derive(Debug, Collect)] +/// Represents information about the Sound when `loadSound()` has been called. +#[derive(Clone, Copy, Collect)] #[collect(no_drop)] -struct QueuedPlay<'gc> { - sound_object: Object<'gc>, - start_offset: f64, - loops: u16, -} +pub struct ExternalSound<'gc>(Gc<'gc, ExternalSoundData>); -#[derive(Debug, Collect)] +#[derive(Collect)] #[collect(no_drop)] -enum SoundState<'gc> { - /// Empty sound object, no sound playback allowed. - Empty, - - /// Sound is loading, plays can be queued. - Loading { queued_plays: Vec> }, - - /// Sound is loaded, plays can be started immediately. - Loaded { - /// The sound that is attached to this object. - #[collect(require_static)] - sound: SoundHandle, - }, +struct ExternalSoundData { + /// Whether this sound is set to be streaming. + /// This will be true if `Sound.loadSound` was called with `isStreaming` of `true`. + /// A streaming sound can only have a single active instance. + is_streaming: Cell, + + /// Whether this sound will autoplay + will_autoplay: Cell, + + /// A dedicated sound transform for this sound. + transform: Cell, + + /// Whether we are currently loading a sound. + is_loading: Cell, + + /// The load generation associated with this external sound. + /// Used to prevent multiple simultaneous loads on one Sound. + load_id: Cell, +} + +impl<'gc> ExternalSound<'gc> { + pub fn empty(mc: &Mutation<'gc>, is_streaming: bool, load_id: u32) -> ExternalSound<'gc> { + ExternalSound(Gc::new( + mc, + ExternalSoundData { + is_streaming: Cell::new(is_streaming), + will_autoplay: Cell::new(is_streaming), + transform: Cell::new(SoundTransform::default()), + is_loading: Cell::new(true), + load_id: Cell::new(load_id), + }, + )) + } + + pub fn is_streaming(self) -> bool { + self.0.is_streaming.get() + } + + pub fn will_autoplay(self) -> bool { + self.0.will_autoplay.get() + } + + pub fn set_will_autoplay(self, will_autoplay: bool) { + self.0.will_autoplay.set(will_autoplay); + } + + pub fn transform(self) -> SoundTransform { + self.0.transform.get() + } + + pub fn set_transform( + self, + context: &mut UpdateContext<'gc>, + sound: Option, + sound_transform: SoundTransform, + ) { + self.0.transform.set(sound_transform); + + if let Some(sound) = sound { + context.set_sound_transform_with_handle(sound, sound_transform); + } + } + + pub fn is_loading(self) -> bool { + self.0.is_loading.get() + } + + pub fn set_is_loading(self, is_loading: bool) { + self.0.is_loading.set(is_loading) + } + + pub fn load_id(self) -> u32 { + self.0.load_id.get() + } } -/// A `Sound` object that is tied to a sound from the `AudioBackend``. +/// A `Sound` object that is tied to a sound from the `AudioBackend`. #[derive(Clone, Copy, Collect)] #[collect(no_drop)] pub struct Sound<'gc>(Gc<'gc, SoundData<'gc>>); @@ -56,7 +114,8 @@ pub struct Sound<'gc>(Gc<'gc, SoundData<'gc>>); #[derive(Collect)] #[collect(no_drop)] struct SoundData<'gc> { - state: RefLock>, + /// The sound that is attached to this object. + sound: Cell>, /// The instance of the last played sound on this object. sound_instance: Cell>, @@ -72,17 +131,19 @@ struct SoundData<'gc> { /// Duration of the currently attached sound in milliseconds. duration: Cell>, - /// Whether this sound is an external streaming MP3. - /// This will be true if `Sound.loadSound` was called with `isStreaming` of `true`. - /// A streaming sound can only have a single active instance. - is_streaming: Cell, + /// Data for playing external sounds on this object. + /// This is `Some` when this sound has had `loadSound()` called on it, + /// and therefore enters a state of being "separated" from its + /// original owner (even if it was controlling global sound initially), + /// and can then only ever control externally loaded sounds. + external: Lock>>, } impl fmt::Debug for Sound<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Sound") .field("ptr", &Gc::as_ptr(self.0)) - .field("state", &self.0.state.borrow()) + .field("sound", &self.0.sound.get()) .field("sound_instance", &self.0.sound_instance.get()) .field("target", &self.0.target) .finish() @@ -94,12 +155,12 @@ impl<'gc> Sound<'gc> { Sound(Gc::new( mc, SoundData { - state: RefLock::new(SoundState::Empty), + sound: Cell::new(None), sound_instance: Cell::new(None), target, position: Cell::new(0), duration: Cell::new(None), - is_streaming: Cell::new(false), + external: Lock::new(None), }, )) } @@ -113,10 +174,41 @@ impl<'gc> Sound<'gc> { } pub fn sound(self) -> Option { - if let SoundState::Loaded { sound } = *self.0.state.borrow() { - Some(sound) - } else { - None + self.0.sound.get() + } + + pub fn set_sound( + self, + activation: &mut Activation<'_, 'gc>, + sound_object: Object<'gc>, + sound: Option, + ) { + self.0.sound.set(sound); + + // `position` and `duration` are only defined when a sound is loaded. + if !sound_object.has_property(activation, istr!("position")) + || !sound_object.has_property(activation, istr!("duration")) + { + let fn_proto = activation.prototypes().function; + let getter_position = + FunctionObject::native(position).build(activation.strings(), fn_proto, None); + let getter_duration = + FunctionObject::native(duration).build(activation.strings(), fn_proto, None); + + sound_object.add_property( + activation.gc(), + istr!("position"), + getter_position, + None, + Attribute::DONT_ENUM | Attribute::DONT_DELETE, + ); + sound_object.add_property( + activation.gc(), + istr!("duration"), + getter_duration, + None, + Attribute::DONT_ENUM | Attribute::DONT_DELETE, + ); } } @@ -128,8 +220,14 @@ impl<'gc> Sound<'gc> { self.0.sound_instance.set(sound_instance); } + /// Tries to resolve the display object at the `target` path and returns it. + /// + /// Note that this will also return `None` if this object is set to + /// play external audio, even if the target display object exists. pub fn owner(self, activation: &mut Activation<'_, 'gc>) -> Option> { - if let Some(target) = self.0.target { + if let Some(target) = self.0.target + && !self.is_external() + { let start_clip = activation.target_clip_or_root(); activation .resolve_target_display_object(start_clip, Value::String(target), false) @@ -143,20 +241,24 @@ impl<'gc> Sound<'gc> { self.0.target.is_none() } - /// Returns `true` if this sound is attached to the global sound - /// (initial target was `Null` or `Undefined`), or if the target - /// *currently* resolves to a valid display object. + /// Returns `true` if any one of the following is true: + /// - We are controlling global sound. + /// - We are playing an external MP3 sound. + /// - The `target` *currently* resolves to a valid display object. pub fn has_valid_owner(self, activation: &mut Activation<'_, 'gc>) -> bool { - self.use_global_sound() || self.owner(activation).is_some() + self.use_global_sound() || self.is_external() || self.owner(activation).is_some() } - /// Gets the sound transform for this sound. - /// If this sound is set to use global sound, then the - /// global sound transform will be returned. Otherwise, - /// it will try to resolve the owner and get its sound - /// transform. If it can't, then `None` is returned. + /// Gets the sound transform for this sound. It goes through the following in order: + /// 1. If we have our own sound transform (from `loadSound()` being called), return that. + /// 2. If not, then if we are controlling global sound, return the global sound transform. + /// 3. If not, then return the sound transform of our `owner`, if we have one. + /// 4. If not, then return `None`. pub fn sound_transform(self, activation: &mut Activation<'_, 'gc>) -> Option { - if self.use_global_sound() { + // `external` must be checked first since it takes precedence over every other case + if let Some(external) = self.external() { + Some(external.transform()) + } else if self.use_global_sound() { Some(*activation.context.global_sound_transform()) } else if let Some(owner) = self.owner(activation) { Some(owner.base().sound_transform()) @@ -173,106 +275,19 @@ impl<'gc> Sound<'gc> { self.0.position.set(position); } - pub fn is_streaming(self) -> bool { - self.0.is_streaming.get() + pub fn external(self) -> Option> { + self.0.external.get() } - pub fn set_is_streaming(self, is_streaming: bool) { - self.0.is_streaming.set(is_streaming); + pub fn set_external(self, mc: &Mutation<'gc>, external_sound: ExternalSound<'gc>) { + let write = Gc::write(mc, self.0); + unlock!(write, SoundData, external).set(Some(external_sound)); } - fn play(self, play: QueuedPlay<'gc>, activation: &mut Activation<'_, 'gc>) { - let write = Gc::write(activation.context.gc(), self.0); - let sound_handle = match &mut *unlock!(write, SoundData, state).borrow_mut() { - SoundState::Empty => { - tracing::warn!("Ignoring Sound playback, because it's not loaded."); - return; - } - SoundState::Loading { queued_plays } => { - queued_plays.push(play); - return; - } - SoundState::Loaded { sound } => *sound, - }; - - if self.is_streaming() { - // Streaming MP3s can only have a single active instance. - if let Some(sound_instance) = self.sound_instance() { - activation.context.stop_sound(sound_instance); - } - } - - let owner = self.owner(activation); - let sound_instance = activation.context.start_sound( - sound_handle, - &swf::SoundInfo { - event: swf::SoundEvent::Start, - in_sample: if play.start_offset > 0.0 { - Some((play.start_offset * 44100.0) as u32) - } else { - None - }, - out_sample: None, - num_loops: play.loops, - envelope: None, - }, - None, - owner, - Some(play.sound_object), - ); - if sound_instance.is_some() { - self.set_sound_instance(sound_instance); - } - } - - fn set_is_loading(self, context: &mut UpdateContext<'gc>) { - // All queued plays are discarded at this point. - let new_data = SoundState::Loading { - queued_plays: Vec::new(), - }; - unlock!(Gc::write(context.gc(), self.0), SoundData, state).replace(new_data); - } - - pub fn load_sound( - self, - activation: &mut Activation<'_, 'gc>, - sound_object: Object<'gc>, - sound: SoundHandle, - ) { - let new_data = SoundState::Loaded { sound }; - let write = Gc::write(activation.gc(), self.0); - let old_data = unlock!(write, SoundData, state).replace(new_data); - - if let SoundState::Loading { queued_plays } = old_data { - for play in queued_plays { - self.play(play, activation); - } - } - - if !sound_object.has_property(activation, istr!("position")) - || !sound_object.has_property(activation, istr!("duration")) - { - let fn_proto = activation.prototypes().function; - let getter_position = - FunctionObject::native(position).build(activation.strings(), fn_proto, None); - let getter_duration = - FunctionObject::native(duration).build(activation.strings(), fn_proto, None); - - sound_object.add_property( - activation.gc(), - istr!("position"), - getter_position, - None, - Attribute::DONT_ENUM | Attribute::DONT_DELETE, - ); - sound_object.add_property( - activation.gc(), - istr!("duration"), - getter_duration, - None, - Attribute::DONT_ENUM | Attribute::DONT_DELETE, - ); - } + /// Whether this Sound is set to play external audio + /// (`loadSound()` has been called on it). + pub fn is_external(self) -> bool { + self.external().is_some() } pub fn load_id3( @@ -361,8 +376,8 @@ impl<'gc> Sound<'gc> { const PROTO_DECLS: StaticDeclarations = declare_static_properties! { // Note: id3 is not a built-in property. See [`Sound::load_id3`]. - // Note: duration is defined later. See [`Sound::load_sound`]. - // Note: position is defined later. See [`Sound::load_sound`]. + // Note: duration is defined later. See [`Sound::set_sound`]. + // Note: position is defined later. See [`Sound::set_sound`]. "getPan" => method(get_pan; DONT_ENUM | DONT_DELETE | READ_ONLY); "getTransform" => method(get_transform; DONT_ENUM | DONT_DELETE | READ_ONLY); "getVolume" => method(get_volume; DONT_ENUM | DONT_DELETE | READ_ONLY); @@ -431,7 +446,11 @@ fn attach_sound<'gc>( .unwrap_or(&Value::Undefined) .coerce_to_string(activation)?; - let movie = if sound.use_global_sound() { + // A Sound that has had `loadSound()` called on it + // can never again have sound characters attached to it. + // This check works because `owner` always returns `None` + // when `is_external()` is true. + let movie = if sound.use_global_sound() && !sound.is_external() { activation.base_clip().avm1_root().movie() } else if let Some(owner) = sound.owner(activation) { owner.movie() @@ -445,8 +464,7 @@ fn attach_sound<'gc>( .library_for_movie_mut(movie) .character_by_export_name(name) { - sound.load_sound(activation, this, sound_handle); - sound.set_is_streaming(false); + sound.set_sound(activation, this, Some(sound_handle)); sound.set_duration( activation .context @@ -572,20 +590,67 @@ fn load_sound<'gc>( .get(1) .unwrap_or(&Value::Undefined) .as_bool(activation.swf_version()); - if is_streaming { - // Streaming MP3s can only have a single active instance. + + let mut load_id = 0; + + if let Some(external) = sound.external() { + // Any external sound instances that are playing + // are stopped when `loadSound` is called again, streaming or not. // (Previous `attachSound` instances will continue to play.) - if let Some(sound_instance) = sound.sound_instance() { - activation.context.stop_sound(sound_instance); + if let Some(sound) = sound.sound() { + activation.context.stop_sounds_with_handle(sound); + } + + // If a load on this Sound is already in progress, + // onLoad gets called with success as `false`. + if external.is_loading() { + let _ = this.call_method( + istr!("onLoad"), + &[false.into()], + activation, + ExecutionReason::Special, + ); + } + + // Add 1 to the load counter for this Sound + load_id = external.load_id() + 1; + } + + sound.set_external( + activation.gc(), + ExternalSound::empty(activation.gc(), is_streaming, load_id), + ); + + let request_url = url.to_utf8_lossy().into_owned(); + + // Local files are loaded synchronously on the FP desktop projector; + // that is, execution is paused until the load has completed. + // TODO: This check should probably be implemented into `NavigatorBackend`. + #[cfg(not(target_family = "wasm"))] + { + let is_blocking = activation + .context + .navigator + .resolve_url(&request_url) + .ok() + .is_some_and(|resolved| resolved.scheme() == "file"); + + if is_blocking { + let _ = crate::loader::load_sound_avm1_blocking( + activation.context, + this, + Request::get(request_url), + load_id, + ); + return Ok(Value::Undefined); } } - sound.set_is_streaming(is_streaming); - sound.set_is_loading(activation.context); + let future = crate::loader::load_sound_avm1( activation.context, this, - Request::get(url.to_utf8_lossy().into_owned()), - is_streaming, + Request::get(request_url), + load_id, ); activation.context.navigator.spawn_future(future); } @@ -620,7 +685,13 @@ fn set_pan<'gc>( .unwrap_or(&0.into()) .coerce_to_f64(activation)? .clamp_to_i32(); - if let Some(owner) = sound.owner(activation) { + if let Some(external) = sound.external() { + external.set_transform( + activation.context, + sound.sound(), + external.transform().with_pan(pan), + ); + } else if let Some(owner) = sound.owner(activation) { let transform = owner.base().sound_transform(); owner.set_sound_transform(activation.context, transform.with_pan(pan)); } else if sound.use_global_sound() { @@ -647,14 +718,19 @@ fn set_transform<'gc>( let owner = sound.owner(activation); - if owner.is_none() && !sound.use_global_sound() { + if owner.is_none() && !sound.use_global_sound() && !sound.is_external() { return Ok(Value::Undefined); } - let mut transform = owner.map_or_else( - || *activation.context.global_sound_transform(), - |owner| owner.base().sound_transform(), - ); + // Check first if we have our own sound transform. + // If we don't, then see if we currently have a valid owner. + // If not, then use the global sound transform. + let mut transform = sound.external().map(|e| e.transform()).unwrap_or_else(|| { + owner.map_or_else( + || *activation.context.global_sound_transform(), + |owner| owner.base().sound_transform(), + ) + }); if obj.has_own_property(activation, istr!("ll")) { transform.left_to_left = obj @@ -678,7 +754,9 @@ fn set_transform<'gc>( .coerce_to_i32(activation)?; } - if let Some(owner) = owner { + if let Some(external) = sound.external() { + external.set_transform(activation.context, sound.sound(), transform); + } else if let Some(owner) = owner { owner.set_sound_transform(activation.context, transform); } else { activation.context.set_global_sound_transform(transform); @@ -698,7 +776,13 @@ fn set_volume<'gc>( .unwrap_or(&0.into()) .coerce_to_f64(activation)? .clamp_to_i32(); - if let Some(owner) = sound.owner(activation) { + if let Some(external) = sound.external() { + let transform = SoundTransform { + volume, + ..external.transform() + }; + external.set_transform(activation.context, sound.sound(), transform); + } else if let Some(owner) = sound.owner(activation) { let transform = SoundTransform { volume, ..owner.base().sound_transform() @@ -723,17 +807,49 @@ pub fn start<'gc>( ) -> Result, Error<'gc>> { if let NativeObject::Sound(sound) = this.native() { if sound.has_valid_owner(activation) { + let is_streaming = sound.external().is_some_and(|e| e.is_streaming()); + let start_offset = args.get(0).unwrap_or(&0.into()).coerce_to_f64(activation)?; - let loops = args.get(1).unwrap_or(&1.into()).coerce_to_f64(activation)?; + let loops = args + .get(1) + // Loops are ignored if the sound is streaming, per the docs. + .filter(|_| !is_streaming) + .unwrap_or(&1.into()) + .coerce_to_f64(activation)?; // TODO: Handle loops > u16::MAX. let loops = (loops as u16).max(1); - let play = QueuedPlay { - sound_object: this, - start_offset, - loops, - }; - sound.play(play, activation); + if let Some(sound_handle) = sound.sound() { + if is_streaming { + // Streaming MP3s can only have a single active instance. + if let Some(sound_instance) = sound.sound_instance() { + activation.context.stop_sound(sound_instance); + } + } + let owner = sound.owner(activation); + let sound_instance = activation.context.start_sound( + sound_handle, + &swf::SoundInfo { + event: swf::SoundEvent::Start, + in_sample: if start_offset > 0.0 { + Some((start_offset * 44100.0) as u32) + } else { + None + }, + out_sample: None, + num_loops: loops, + envelope: None, + }, + sound.external().map(|e| e.transform()), + owner, + Some(this), + ); + if sound_instance.is_some() { + sound.set_sound_instance(sound_instance); + } + } else { + avm_warn!(activation, "Sound.start: No sound is attached"); + } } } else { avm_warn!(activation, "Sound.start: Invalid sound"); @@ -748,11 +864,15 @@ fn stop<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let NativeObject::Sound(sound) = this.native() { + if let Some(external) = sound.external() { + external.set_will_autoplay(false); + } if let Some(name) = args.get(0) { // Usage 1: Stop all instances of a particular sound, using the name parameter. + // If this parameter is given, but we're playing external sound, then do nothing. let name = name.coerce_to_string(activation)?; - let movie = if sound.use_global_sound() { + let movie = if sound.use_global_sound() && !sound.is_external() { activation.base_clip().avm1_root().movie() } else if let Some(owner) = sound.owner(activation) { owner.movie() @@ -766,20 +886,27 @@ fn stop<'gc>( .library_for_movie_mut(movie) .character_by_export_name(name) { - // TODO: This isn't entirely correct. We should only + // FIXME: This isn't entirely correct. We should only // stop sounds with this name on this particular sound object; // right now we're stopping *all* sound objects playing this sound. activation.context.stop_sounds_with_handle(sound); } else { avm_warn!(activation, "Sound.stop: Sound '{}' not found", name); } + } else if sound.is_external() { + // Usage 2: If there is no name and we're playing external sound, + // then stop any instances of that sound. + if let Some(sound) = sound.sound() { + activation.context.stop_sounds_with_handle(sound); + } + sound.set_sound_instance(None); } else if let Some(owner) = sound.owner(activation) { - // Usage 2: If there is no name and we have an owner, + // Usage 3: If there is no name and we have an owner, // then stop all sound running within a given clip. activation.context.stop_sounds_with_display_object(owner); sound.set_sound_instance(None); } else if sound.use_global_sound() { - // Usage 3: If there is no name and we are linked to global sound, + // Usage 4: If there is no name and we are linked to global sound, // this call acts like `stopAllSounds()`. activation.context.stop_all_sounds(); } diff --git a/core/src/backend/audio.rs b/core/src/backend/audio.rs index 41f6840ab56b2..0f63f6b114baa 100644 --- a/core/src/backend/audio.rs +++ b/core/src/backend/audio.rs @@ -732,6 +732,25 @@ impl<'gc> AudioManager<'gc> { } } + pub fn set_sound_transform_with_handle( + &mut self, + sound: SoundHandle, + sound_transform: display_object::SoundTransform, + ) { + let mut changed = false; + + for other in &mut self.sounds { + if other.sound == Some(sound) { + other.transform = sound_transform; + changed = true; + } + } + + if changed { + self.transforms_dirty = true; + } + } + /// Returns the number of seconds that a timeline audio stream should buffer before playing. /// /// Currently unused by Ruffle. @@ -839,9 +858,10 @@ pub struct SoundInstance<'gc> { /// The local sound transform of this sound. /// - /// Only AVM2 sounds have a local sound transform. In AVM1, sound instances - /// instead get the sound transform of the display object they're - /// associated with. + /// AVM2 sounds only have a local sound transform. In AVM1, sound instances + /// have a local transform if they have loaded a sound with `loadSound()`, + /// otherwise they get the sound transform of the display object + /// they're associated with. #[collect(require_static)] transform: display_object::SoundTransform, diff --git a/core/src/context.rs b/core/src/context.rs index b24e67dc50190..310a750ea6ed2 100644 --- a/core/src/context.rs +++ b/core/src/context.rs @@ -270,6 +270,16 @@ impl<'gc> UpdateContext<'gc> { .set_local_sound_transform(instance, sound_transform); } + /// Set the local sound transform for each instance in a handle. + pub fn set_sound_transform_with_handle( + &mut self, + sound: SoundHandle, + sound_transform: SoundTransform, + ) { + self.audio_manager + .set_sound_transform_with_handle(sound, sound_transform); + } + pub fn start_sound( &mut self, sound: SoundHandle, diff --git a/core/src/loader.rs b/core/src/loader.rs index 3023b045c0fe9..64cd9d8ee2227 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -1373,7 +1373,7 @@ pub fn load_sound_avm1<'gc>( uc: &UpdateContext<'gc>, sound_object: Object<'gc>, request: Request, - is_streaming: bool, + load_id: u32, ) -> OwnedFuture<(), Error> { let player = uc.player_handle(); let sound_object = ObjectHandle::stash(uc, sound_object); @@ -1383,47 +1383,87 @@ pub fn load_sound_avm1<'gc>( let response = wait_for_full_response(fetch).await; // Fire the load handler. - player.lock().unwrap().update(|uc| { - let sound_object = sound_object.fetch(uc); + player + .lock() + .unwrap() + .update(|uc| load_sound_avm1_data(uc, sound_object, response, load_id)) + }) +} - let NativeObject::Sound(sound) = sound_object.native() else { - panic!("NativeObject must be Sound"); - }; +/// Kick off a synchronous AVM1 audio load. +/// This will block execution until the sound has been loaded. +#[cfg(not(target_family = "wasm"))] +pub fn load_sound_avm1_blocking<'gc>( + uc: &mut UpdateContext<'gc>, + sound_object: Object<'gc>, + request: Request, + load_id: u32, +) -> Result<(), Error> { + let sound_object = ObjectHandle::stash(uc, sound_object); - let mut activation = Activation::from_stub(uc, ActivationIdentifier::root("[Loader]")); + let fetch = uc.navigator.fetch(request); + let response = futures::executor::block_on(wait_for_full_response(fetch)); - let success = response - .map_err(|e| e.error) - .and_then(|(body, _, _, _)| { - let handle = activation.context.audio.register_mp3(&body)?; - sound.load_sound(&mut activation, sound_object, handle); - sound.set_duration(Some(0)); - sound.load_id3(&mut activation, sound_object, &body)?; - let duration = activation - .context - .audio - .get_sound_duration(handle) - .map(|d| d.as_millis().round() as u32); - sound.set_duration(duration); - Ok(()) - }) - .is_ok(); + load_sound_avm1_data(uc, sound_object, response, load_id) +} - let _ = sound_object.call_method( - istr!("onLoad"), - &[success.into()], - &mut activation, - ExecutionReason::Special, - ); +fn load_sound_avm1_data<'gc>( + uc: &mut UpdateContext<'gc>, + sound_object: ObjectHandle, + response: Result<(Vec, String, u16, bool), ErrorResponse>, + load_id: u32, +) -> Result<(), Error> { + let sound_object = sound_object.fetch(uc); - // Streaming sounds should auto-play. - if is_streaming { - crate::avm1::start_sound(&mut activation, sound_object, &[])?; - } + let NativeObject::Sound(sound) = sound_object.native() else { + panic!("NativeObject must be Sound"); + }; + + let mut activation = Activation::from_stub(uc, ActivationIdentifier::root("[Loader]")); + + let external = sound + .external() + .expect("Loaded sound should have external sound data"); + + // If the load_id has changed, then it means loadSound() was called again + // before this load completed, so we need to stop here. + if external.load_id() != load_id { + return Ok(()); + } + let success = response + .map_err(|e| e.error) + .and_then(|(body, _, _, _)| { + let handle = activation.context.audio.register_mp3(&body)?; + sound.set_sound(&mut activation, sound_object, Some(handle)); + sound.set_duration(Some(0)); + sound.load_id3(&mut activation, sound_object, &body)?; + let duration = activation + .context + .audio + .get_sound_duration(handle) + .map(|d| d.as_millis().round() as u32); + sound.set_duration(duration); Ok(()) }) - }) + .is_ok(); + + external.set_is_loading(false); + + let _ = sound_object.call_method( + istr!("onLoad"), + &[success.into()], + &mut activation, + ExecutionReason::Special, + ); + + // Streaming sounds should auto-play, + // unless stop() has been called before it finished loading. + if external.will_autoplay() { + crate::avm1::start_sound(&mut activation, sound_object, &[])?; + } + + Ok(()) } /// Kick off an AVM2 audio load. diff --git a/tests/tests/swfs/avm1/sound_load_multiple_instances/output.txt b/tests/tests/swfs/avm1/sound_load_multiple_instances/output.txt new file mode 100644 index 0000000000000..33d15cd96c60c --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_multiple_instances/output.txt @@ -0,0 +1,19 @@ +frame 1 - loading sound.mp3 +frame 2 +frame 3 - starting more instances +frame 4 +frame 5 - muting volume +frame 6 +frame 7 - calling loadSound again +frame 8 +frame 9 - calling loadSound with streaming +frame 10 +frame 11 - starting more instances +frame 12 +frame 13 - calling loadSound, stopped +frame 14 +frame 15 - start +frame 16 +frame 17 - starting another +frame 18 - stopping +frame 19 diff --git a/tests/tests/swfs/avm1/sound_load_multiple_instances/sound.mp3 b/tests/tests/swfs/avm1/sound_load_multiple_instances/sound.mp3 new file mode 100644 index 0000000000000..893eeff8dcb78 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_load_multiple_instances/sound.mp3 differ diff --git a/tests/tests/swfs/avm1/sound_load_multiple_instances/test.swf b/tests/tests/swfs/avm1/sound_load_multiple_instances/test.swf new file mode 100644 index 0000000000000..56ecd63bb4722 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_load_multiple_instances/test.swf differ diff --git a/tests/tests/swfs/avm1/sound_load_multiple_instances/test.toml b/tests/tests/swfs/avm1/sound_load_multiple_instances/test.toml new file mode 100644 index 0000000000000..cc0b3858a50ed --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_multiple_instances/test.toml @@ -0,0 +1,23 @@ +num_frames = 19 + +[player_options] +with_audio = true + +[audio_assertions.non_streaming_vol_50] +frames = [2] +min_max_amplitude = 0.29 +max_amplitude = 0.31 + +[audio_assertions.non_streaming_multiple] +frames = [4] +min_max_amplitude = 1.76 +max_amplitude = 1.78 + +[audio_assertions.single_vol_full] +frames = [8,10,12,16] +min_max_amplitude = 0.59 +max_amplitude = 0.61 + +[audio_assertions.silence] +frames = [6,14,19] +max_amplitude = 0.0 diff --git a/tests/tests/swfs/avm1/sound_load_multiple_remote/README.md b/tests/tests/swfs/avm1/sound_load_multiple_remote/README.md new file mode 100644 index 0000000000000..618c96cb9b745 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_multiple_remote/README.md @@ -0,0 +1,12 @@ + # Running the test + + +To be able to serve the MP3 file, run the command `python -m http.server -d localhost` from this directory. Then, run 'test.swf' in either Flash Player or the Ruffle Desktop player. + +When running under flash player, you'll need to allow the SWF to make network connections. On Linux, this can be done by creating the file `/etc/adobe/FlashPlayerTrust/test.cfg` with the following contents: + +``` +/ancestor/of/swf/path +``` + +where `ancestor/of/swf/path` is any path that's an ancestor of the path of `test.swf` (e.g. `/home/username/`) diff --git a/tests/tests/swfs/avm1/sound_load_multiple_remote/localhost/noise.mp3 b/tests/tests/swfs/avm1/sound_load_multiple_remote/localhost/noise.mp3 new file mode 100644 index 0000000000000..862f41452ee42 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_load_multiple_remote/localhost/noise.mp3 differ diff --git a/tests/tests/swfs/avm1/sound_load_multiple_remote/output.txt b/tests/tests/swfs/avm1/sound_load_multiple_remote/output.txt new file mode 100644 index 0000000000000..b5ae83f9347c5 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_multiple_remote/output.txt @@ -0,0 +1,6 @@ +loading sound +loading again +onLoad false +after loading again +onLoad true +Sound complete diff --git a/tests/tests/swfs/avm1/sound_load_multiple_remote/test.as b/tests/tests/swfs/avm1/sound_load_multiple_remote/test.as new file mode 100644 index 0000000000000..4f7e8adfbf9c9 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_multiple_remote/test.as @@ -0,0 +1,15 @@ +var sound = new Sound(); +sound.onSoundComplete = function() { + trace("Sound complete"); +}; +sound.onLoad = function(s) { + trace("onLoad " + s); + sound.start(0,9); +}; + +trace("loading sound"); +sound.loadSound("http://localhost:8000/noise.mp3", true); +sound.stop(); +trace("loading again"); +sound.loadSound("http://localhost:8000/noise.mp3", true); +trace("after loading again"); diff --git a/tests/tests/swfs/avm1/sound_load_multiple_remote/test.swf b/tests/tests/swfs/avm1/sound_load_multiple_remote/test.swf new file mode 100644 index 0000000000000..d5721b18727a9 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_load_multiple_remote/test.swf differ diff --git a/tests/tests/swfs/avm1/sound_load_multiple_remote/test.toml b/tests/tests/swfs/avm1/sound_load_multiple_remote/test.toml new file mode 100644 index 0000000000000..a3e11406d5c93 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_multiple_remote/test.toml @@ -0,0 +1,11 @@ +num_ticks = 150 + +[player_options] +with_audio = true + +[[compilers]] +type = "Rascal" +target = "test.swf" +scripts = ["test.as"] +swf_version = 15 +use_network = true diff --git a/tests/tests/swfs/avm1/sound_load_props/output.txt b/tests/tests/swfs/avm1/sound_load_props/output.txt new file mode 100644 index 0000000000000..02aee9cdc4ba9 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_props/output.txt @@ -0,0 +1,112 @@ +// s_mc.getVolume() +undefined +// s_mc.getPan() +undefined +// s_mc.getTransform() +undefined + +// s_mc.getVolume() +50 +// s_mc.getPan() +90 +// s_mc.getTransform() +[object Object] + rl: 40 + rr: 30 + lr: 20 + ll: 10 + +// s_mc.loadSound() + +// s_mc.getVolume() +50 +// s_mc.getPan() +90 +// s_mc.getTransform() +[object Object] + rl: 40 + rr: 30 + lr: 20 + ll: 10 + +// s_mc.loadSound("") + +// s_mc.getVolume() +200 +// s_mc.getPan() +50 +// s_mc.getTransform() +[object Object] + rl: 0 + rr: 100 + lr: 0 + ll: 50 + +// s_mc.loadSound("invalid.mp3") + +// s_mc.getVolume() +100 +// s_mc.getPan() +0 +// s_mc.getTransform() +[object Object] + rl: 0 + rr: 100 + lr: 0 + ll: 100 + +// s_mc.getVolume() +1 +// s_mc.getPan() +97 +// s_mc.getTransform() +[object Object] + rl: 6 + rr: 5 + lr: 4 + ll: 3 + +// s_mc2.getVolume() +50 +// s_mc2.getPan() +90 +// s_mc2.getTransform() +[object Object] + rl: 40 + rr: 30 + lr: 20 + ll: 10 + +// s_glob.getVolume() +100 +// s_glob.getPan() +0 +// s_glob.getTransform() +[object Object] + rl: 0 + rr: 100 + lr: 0 + ll: 100 + +// s_mc2.getVolume() +10 +// s_mc2.getPan() +87 +// s_mc2.getTransform() +[object Object] + rl: 16 + rr: 15 + lr: 14 + ll: 13 + +// s_mc.getVolume() +1 +// s_mc.getPan() +97 +// s_mc.getTransform() +[object Object] + rl: 6 + rr: 5 + lr: 4 + ll: 3 + diff --git a/tests/tests/swfs/avm1/sound_load_props/test.as b/tests/tests/swfs/avm1/sound_load_props/test.as new file mode 100644 index 0000000000000..579d3dde5a1f3 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_props/test.as @@ -0,0 +1,86 @@ +var s_mc = new Sound("mc"); +var s_mc2 = new Sound("mc"); +var s_glob = new Sound(); + +function traceProps(s, n) { + trace("// s_" + n + ".getVolume()"); + trace(s.getVolume()); + trace("// s_" + n + ".getPan()"); + trace(s.getPan()); + trace("// s_" + n + ".getTransform()"); + var t = s.getTransform() + trace(t); + for (var i in t) { + trace(" " + i + ": " + t[i]); + } + trace(""); +} + +s_mc.loadSound("invalid.mp3"); + +traceProps(s_mc, "mc"); + +createEmptyMovieClip("mc", 1); + +s_mc.setVolume(50); +s_mc.setPan(-25); +var o = new Object(); +o.ll = 10 +o.lr = 20 +o.rr = 30 +o.rl = 40 +s_mc.setTransform(o); + +traceProps(s_mc, "mc"); + +trace("// s_mc.loadSound()"); +trace(""); +s_mc.loadSound(); + +traceProps(s_mc, "mc"); + +trace("// s_mc.loadSound(\"\")"); +trace(""); +s_mc.loadSound(""); + +s_mc.setVolume(200); +s_mc.setPan(50); + +traceProps(s_mc, "mc"); + +trace("// s_mc.loadSound(\"invalid.mp3\")"); +trace(""); +s_mc.loadSound("invalid.mp3"); + +traceProps(s_mc, "mc"); + +s_mc.setVolume(1); +s_mc.setPan(-2); +var o = new Object(); +o.ll = 3 +o.lr = 4 +o.rr = 5 +o.rl = 6 +s_mc.setTransform(o); + +traceProps(s_mc, "mc"); + +traceProps(s_mc2, "mc2"); + +traceProps(s_glob, "glob"); + +s_mc2.loadSound("invalid.mp3"); + +s_mc2.setVolume(10); +s_mc2.setPan(-12); +var o = new Object(); +o.ll = 13 +o.lr = 14 +o.rr = 15 +o.rl = 16 +s_mc2.setTransform(o); + +traceProps(s_mc2, "mc2"); +traceProps(s_mc, "mc"); + +fscommand("quit"); diff --git a/tests/tests/swfs/avm1/sound_load_props/test.swf b/tests/tests/swfs/avm1/sound_load_props/test.swf new file mode 100644 index 0000000000000..cd9f1f2d7f1a8 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_load_props/test.swf differ diff --git a/tests/tests/swfs/avm1/sound_load_props/test.toml b/tests/tests/swfs/avm1/sound_load_props/test.toml new file mode 100644 index 0000000000000..167757f3a0514 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_props/test.toml @@ -0,0 +1,7 @@ +num_ticks = 1 + +[[compilers]] +type = "Rascal" +target = "test.swf" +scripts = ["test.as"] +swf_version = 15 diff --git a/tests/tests/swfs/avm1/sound_load_start/output.txt b/tests/tests/swfs/avm1/sound_load_start/output.txt index 1df0ac909aa73..14e9e5871cc92 100644 --- a/tests/tests/swfs/avm1/sound_load_start/output.txt +++ b/tests/tests/swfs/avm1/sound_load_start/output.txt @@ -1,3 +1,6 @@ +before +onLoad +after Sound complete Sound complete Sound complete diff --git a/tests/tests/swfs/avm1/sound_load_start/test.as b/tests/tests/swfs/avm1/sound_load_start/test.as index b2df2fb9d7983..45cd1c5ec40aa 100644 --- a/tests/tests/swfs/avm1/sound_load_start/test.as +++ b/tests/tests/swfs/avm1/sound_load_start/test.as @@ -8,8 +8,14 @@ sound.onSoundComplete = function() { sound.start(); } }; +sound.onLoad = function() { + trace("onLoad"); +}; +trace("before"); sound.loadSound("noise.mp3", false); +trace("after"); + sound.setVolume(50); sound.start(); sound.start(); diff --git a/tests/tests/swfs/avm1/sound_load_start/test.swf b/tests/tests/swfs/avm1/sound_load_start/test.swf index f2af900819182..28d96c3d7cc8e 100644 Binary files a/tests/tests/swfs/avm1/sound_load_start/test.swf and b/tests/tests/swfs/avm1/sound_load_start/test.swf differ diff --git a/tests/tests/swfs/avm1/sound_load_start_remote/README.md b/tests/tests/swfs/avm1/sound_load_start_remote/README.md new file mode 100644 index 0000000000000..618c96cb9b745 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_start_remote/README.md @@ -0,0 +1,12 @@ + # Running the test + + +To be able to serve the MP3 file, run the command `python -m http.server -d localhost` from this directory. Then, run 'test.swf' in either Flash Player or the Ruffle Desktop player. + +When running under flash player, you'll need to allow the SWF to make network connections. On Linux, this can be done by creating the file `/etc/adobe/FlashPlayerTrust/test.cfg` with the following contents: + +``` +/ancestor/of/swf/path +``` + +where `ancestor/of/swf/path` is any path that's an ancestor of the path of `test.swf` (e.g. `/home/username/`) diff --git a/tests/tests/swfs/avm1/sound_load_start_remote/localhost/noise.mp3 b/tests/tests/swfs/avm1/sound_load_start_remote/localhost/noise.mp3 new file mode 100644 index 0000000000000..862f41452ee42 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_load_start_remote/localhost/noise.mp3 differ diff --git a/tests/tests/swfs/avm1/sound_load_start_remote/output.txt b/tests/tests/swfs/avm1/sound_load_start_remote/output.txt new file mode 100644 index 0000000000000..1357f4dc56b19 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_start_remote/output.txt @@ -0,0 +1 @@ +onLoad true diff --git a/tests/tests/swfs/avm1/sound_load_start_remote/test.as b/tests/tests/swfs/avm1/sound_load_start_remote/test.as new file mode 100644 index 0000000000000..f8a595ca7d34f --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_start_remote/test.as @@ -0,0 +1,18 @@ +var i = 0; + +var sound = new Sound(); +sound.onSoundComplete = function() { + trace("Sound complete"); + if (i < 1) { + ++i; + sound.start(); + } +}; +sound.onLoad = function(s) { + trace("onLoad " + s); +}; + +sound.loadSound("http://localhost:8000/noise.mp3"); +sound.setVolume(50); +sound.start(); +sound.start(); diff --git a/tests/tests/swfs/avm1/sound_load_start_remote/test.swf b/tests/tests/swfs/avm1/sound_load_start_remote/test.swf new file mode 100644 index 0000000000000..a7b28252fffb0 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_load_start_remote/test.swf differ diff --git a/tests/tests/swfs/avm1/sound_load_start_remote/test.toml b/tests/tests/swfs/avm1/sound_load_start_remote/test.toml new file mode 100644 index 0000000000000..8ffbf0a128d39 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_start_remote/test.toml @@ -0,0 +1,11 @@ +num_ticks = 100 + +[player_options] +with_audio = true + +[[compilers]] +type = "Rascal" +target = "test.swf" +scripts = ["test.as"] +swf_version = 15 +use_network = true diff --git a/tests/tests/swfs/avm1/sound_load_stops_when_dereferenced/output.txt b/tests/tests/swfs/avm1/sound_load_stops_when_dereferenced/output.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/tests/swfs/avm1/sound_load_stops_when_dereferenced/sound.mp3 b/tests/tests/swfs/avm1/sound_load_stops_when_dereferenced/sound.mp3 new file mode 100644 index 0000000000000..893eeff8dcb78 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_load_stops_when_dereferenced/sound.mp3 differ diff --git a/tests/tests/swfs/avm1/sound_load_stops_when_dereferenced/test.swf b/tests/tests/swfs/avm1/sound_load_stops_when_dereferenced/test.swf new file mode 100644 index 0000000000000..88da6604e4cf6 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_load_stops_when_dereferenced/test.swf differ diff --git a/tests/tests/swfs/avm1/sound_load_stops_when_dereferenced/test.toml b/tests/tests/swfs/avm1/sound_load_stops_when_dereferenced/test.toml new file mode 100644 index 0000000000000..fb6b8092985f8 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_stops_when_dereferenced/test.toml @@ -0,0 +1,13 @@ +num_frames = 2 + +[player_options] +with_audio = true + +# Flash Player automatically stops externally loaded sounds +# when all references to the Sound object that started it are lost. +# Ruffle currently does not do this. + +[audio_assertions.silence] +frames = [2] +max_amplitude = 0.0 +known_failure = true diff --git a/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/README.md b/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/README.md new file mode 100644 index 0000000000000..618c96cb9b745 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/README.md @@ -0,0 +1,12 @@ + # Running the test + + +To be able to serve the MP3 file, run the command `python -m http.server -d localhost` from this directory. Then, run 'test.swf' in either Flash Player or the Ruffle Desktop player. + +When running under flash player, you'll need to allow the SWF to make network connections. On Linux, this can be done by creating the file `/etc/adobe/FlashPlayerTrust/test.cfg` with the following contents: + +``` +/ancestor/of/swf/path +``` + +where `ancestor/of/swf/path` is any path that's an ancestor of the path of `test.swf` (e.g. `/home/username/`) diff --git a/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/localhost/noise.mp3 b/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/localhost/noise.mp3 new file mode 100644 index 0000000000000..862f41452ee42 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/localhost/noise.mp3 differ diff --git a/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/output.txt b/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/output.txt new file mode 100644 index 0000000000000..a5f623ea02b5d --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/output.txt @@ -0,0 +1,3 @@ +before +after +onLoad true diff --git a/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/test.as b/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/test.as new file mode 100644 index 0000000000000..117739cd22760 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/test.as @@ -0,0 +1,12 @@ +var sound = new Sound(); +sound.onSoundComplete = function() { + trace("Sound complete"); +}; +sound.onLoad = function(s) { + trace("onLoad " + s); +}; + +trace("before"); +sound.loadSound("http://localhost:8000/noise.mp3", true); +trace("after"); +sound.stop(); diff --git a/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/test.swf b/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/test.swf new file mode 100644 index 0000000000000..6b86eaaf9facd Binary files /dev/null and b/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/test.swf differ diff --git a/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/test.toml b/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/test.toml new file mode 100644 index 0000000000000..a3e11406d5c93 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_load_streaming_stop_remote/test.toml @@ -0,0 +1,11 @@ +num_ticks = 150 + +[player_options] +with_audio = true + +[[compilers]] +type = "Rascal" +target = "test.swf" +scripts = ["test.as"] +swf_version = 15 +use_network = true diff --git a/tests/tests/swfs/avm1/sound_mixed_attach_load/noise.mp3 b/tests/tests/swfs/avm1/sound_mixed_attach_load/noise.mp3 new file mode 100644 index 0000000000000..862f41452ee42 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_mixed_attach_load/noise.mp3 differ diff --git a/tests/tests/swfs/avm1/sound_mixed_attach_load/output.txt b/tests/tests/swfs/avm1/sound_mixed_attach_load/output.txt new file mode 100644 index 0000000000000..22b297aa13fb6 --- /dev/null +++ b/tests/tests/swfs/avm1/sound_mixed_attach_load/output.txt @@ -0,0 +1,13 @@ +frame 1 - attaching sound +frame 2 +frame 3 - loading noise then stopping +frame 4 +frame 5 - starting 3 instances +frame 6 +frame 7 - muting volume +frame 8 +frame 9 - loading again, as streaming this time +frame 10 +frame 11 - trying to start multiple +frame 12 +frame 14 - only the original attached should be playing diff --git a/tests/tests/swfs/avm1/sound_mixed_attach_load/sound.mp3 b/tests/tests/swfs/avm1/sound_mixed_attach_load/sound.mp3 new file mode 100644 index 0000000000000..893eeff8dcb78 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_mixed_attach_load/sound.mp3 differ diff --git a/tests/tests/swfs/avm1/sound_mixed_attach_load/test.swf b/tests/tests/swfs/avm1/sound_mixed_attach_load/test.swf new file mode 100644 index 0000000000000..a5a3900762638 Binary files /dev/null and b/tests/tests/swfs/avm1/sound_mixed_attach_load/test.swf differ diff --git a/tests/tests/swfs/avm1/sound_mixed_attach_load/test.toml b/tests/tests/swfs/avm1/sound_mixed_attach_load/test.toml new file mode 100644 index 0000000000000..b5d51164d93dd --- /dev/null +++ b/tests/tests/swfs/avm1/sound_mixed_attach_load/test.toml @@ -0,0 +1,19 @@ +num_frames = 14 + +[player_options] +with_audio = true + +[audio_assertions.only_attached] +frames = [6] +min_max_amplitude = 3.34 +max_amplitude = 3.35 + +[audio_assertions.multiple_non_streaming] +frames = [2,4,8,10,14] +min_max_amplitude = 0.59 +max_amplitude = 0.60 + +[audio_assertions.single_streaming] +frames = [12] +min_max_amplitude = 1.46 +max_amplitude = 1.47 diff --git a/tests/tests/swfs/avm1/sound_start_stop/test.toml b/tests/tests/swfs/avm1/sound_start_stop/test.toml index e04868c799613..e3574c6ce09bf 100644 --- a/tests/tests/swfs/avm1/sound_start_stop/test.toml +++ b/tests/tests/swfs/avm1/sound_start_stop/test.toml @@ -5,7 +5,7 @@ with_audio = true [audio_assertions.sound] frames = [3,4] -min_max_amplitude = 0.6 +min_max_amplitude = 0.59 [audio_assertions.silence] frames = [1,2,5] diff --git a/tests/tests/swfs/from_gnash/actionscript.all/Sound-v6/output.ruffle.txt b/tests/tests/swfs/from_gnash/actionscript.all/Sound-v6/output.ruffle.txt index 828b57e6d307c..579763bb028e3 100644 --- a/tests/tests/swfs/from_gnash/actionscript.all/Sound-v6/output.ruffle.txt +++ b/tests/tests/swfs/from_gnash/actionscript.all/Sound-v6/output.ruffle.txt @@ -100,17 +100,22 @@ FAILED: expected: "undefined" obtained: number [./Sound.as:337] PASSED: typeof(s.duration) == "undefined" [./Sound.as:338] PASSED: typeof(s.getPosition()) == "undefined" [./Sound.as:339] PASSED: typeof(s.getDuration()) == "undefined" [./Sound.as:340] -PASSED: typeof(s.getBytesTotal()) == "number" [./Sound.as:345] -PASSED: typeof(s.getBytesLoaded()) == "number" [./Sound.as:346] -FAILED: expected: "number" obtained: undefined [./Sound.as:347] -FAILED: expected: "number" obtained: undefined [./Sound.as:348] -FAILED: expected: "number" obtained: undefined [./Sound.as:349] -Waiting 3 seconds for onSoundComplete to be called onLoad called FAILED: expected: 209 obtained: 0 [./Sound.as:328] PASSED: s.position == 0 [./Sound.as:329] -FAILED: no onSoundComplete arrived after 3 seconds -FAILED: Tests run 107 (expected 112) [ [./Sound.as:31]] -#passed: 84 -#failed: 24 -#total tests run: 108 +PASSED: typeof(s.getBytesTotal()) == "number" [./Sound.as:345] +PASSED: typeof(s.getBytesLoaded()) == "number" [./Sound.as:346] +PASSED: typeof(s.getPosition()) == "number" [./Sound.as:347] +PASSED: typeof(s.duration) == "number" [./Sound.as:348] +PASSED: typeof(s.getDuration()) == "number" [./Sound.as:349] +Waiting 3 seconds for onSoundComplete to be called +onSoundComplete called +PASSED: onSoundComplete called +FAILED: expected: 209 obtained: 0 [./Sound.as:296] +PASSED: s.onLoadCalled [./Sound.as:300] +PASSED: typeof(s.onLoadArg) == 'boolean' [./Sound.as:301] +PASSED: s.onLoadArg == true [./Sound.as:302] +PASSED: mp3 over one minute long loaded +#passed: 92 +#failed: 20 +#total tests run: 112 diff --git a/tests/tests/swfs/from_gnash/actionscript.all/Sound-v6/test.toml b/tests/tests/swfs/from_gnash/actionscript.all/Sound-v6/test.toml index a276e4d210272..f45c9f3797fe7 100644 --- a/tests/tests/swfs/from_gnash/actionscript.all/Sound-v6/test.toml +++ b/tests/tests/swfs/from_gnash/actionscript.all/Sound-v6/test.toml @@ -1,2 +1,5 @@ num_ticks = 50 known_failure = true + +[player_options] +with_audio = true diff --git a/tests/tests/swfs/from_gnash/actionscript.all/Sound-v7/output.ruffle.txt b/tests/tests/swfs/from_gnash/actionscript.all/Sound-v7/output.ruffle.txt index 5e8ce371bfdd4..3becefd1076ab 100644 --- a/tests/tests/swfs/from_gnash/actionscript.all/Sound-v7/output.ruffle.txt +++ b/tests/tests/swfs/from_gnash/actionscript.all/Sound-v7/output.ruffle.txt @@ -100,17 +100,22 @@ FAILED: expected: "undefined" obtained: number [./Sound.as:337] PASSED: typeof(s.duration) == "undefined" [./Sound.as:338] PASSED: typeof(s.getPosition()) == "undefined" [./Sound.as:339] PASSED: typeof(s.getDuration()) == "undefined" [./Sound.as:340] -PASSED: typeof(s.getBytesTotal()) == "number" [./Sound.as:345] -PASSED: typeof(s.getBytesLoaded()) == "number" [./Sound.as:346] -FAILED: expected: "number" obtained: undefined [./Sound.as:347] -FAILED: expected: "number" obtained: undefined [./Sound.as:348] -FAILED: expected: "number" obtained: undefined [./Sound.as:349] -Waiting 3 seconds for onSoundComplete to be called onLoad called FAILED: expected: 209 obtained: 0 [./Sound.as:328] PASSED: s.position == 0 [./Sound.as:329] -FAILED: no onSoundComplete arrived after 3 seconds -FAILED: Tests run 107 (expected 112) [ [./Sound.as:31]] -#passed: 84 -#failed: 24 -#total tests run: 108 +PASSED: typeof(s.getBytesTotal()) == "number" [./Sound.as:345] +PASSED: typeof(s.getBytesLoaded()) == "number" [./Sound.as:346] +PASSED: typeof(s.getPosition()) == "number" [./Sound.as:347] +PASSED: typeof(s.duration) == "number" [./Sound.as:348] +PASSED: typeof(s.getDuration()) == "number" [./Sound.as:349] +Waiting 3 seconds for onSoundComplete to be called +onSoundComplete called +PASSED: onSoundComplete called +FAILED: expected: 209 obtained: 0 [./Sound.as:296] +PASSED: s.onLoadCalled [./Sound.as:300] +PASSED: typeof(s.onLoadArg) == 'boolean' [./Sound.as:301] +PASSED: s.onLoadArg == true [./Sound.as:302] +PASSED: mp3 over one minute long loaded +#passed: 92 +#failed: 20 +#total tests run: 112 diff --git a/tests/tests/swfs/from_gnash/actionscript.all/Sound-v7/test.toml b/tests/tests/swfs/from_gnash/actionscript.all/Sound-v7/test.toml index a276e4d210272..f45c9f3797fe7 100644 --- a/tests/tests/swfs/from_gnash/actionscript.all/Sound-v7/test.toml +++ b/tests/tests/swfs/from_gnash/actionscript.all/Sound-v7/test.toml @@ -1,2 +1,5 @@ num_ticks = 50 known_failure = true + +[player_options] +with_audio = true diff --git a/tests/tests/swfs/from_gnash/actionscript.all/Sound-v8/output.ruffle.txt b/tests/tests/swfs/from_gnash/actionscript.all/Sound-v8/output.ruffle.txt index 1985f485fcb52..2593e672f1220 100644 --- a/tests/tests/swfs/from_gnash/actionscript.all/Sound-v8/output.ruffle.txt +++ b/tests/tests/swfs/from_gnash/actionscript.all/Sound-v8/output.ruffle.txt @@ -100,17 +100,22 @@ FAILED: expected: "undefined" obtained: number [./Sound.as:337] PASSED: typeof(s.duration) == "undefined" [./Sound.as:338] PASSED: typeof(s.getPosition()) == "undefined" [./Sound.as:339] PASSED: typeof(s.getDuration()) == "undefined" [./Sound.as:340] -PASSED: typeof(s.getBytesTotal()) == "number" [./Sound.as:345] -PASSED: typeof(s.getBytesLoaded()) == "number" [./Sound.as:346] -FAILED: expected: "number" obtained: undefined [./Sound.as:347] -FAILED: expected: "number" obtained: undefined [./Sound.as:348] -FAILED: expected: "number" obtained: undefined [./Sound.as:349] -Waiting 3 seconds for onSoundComplete to be called onLoad called FAILED: expected: 209 obtained: 0 [./Sound.as:328] PASSED: s.position == 0 [./Sound.as:329] -FAILED: no onSoundComplete arrived after 3 seconds -FAILED: Tests run 107 (expected 112) [ [./Sound.as:31]] -#passed: 84 -#failed: 24 -#total tests run: 108 +PASSED: typeof(s.getBytesTotal()) == "number" [./Sound.as:345] +PASSED: typeof(s.getBytesLoaded()) == "number" [./Sound.as:346] +PASSED: typeof(s.getPosition()) == "number" [./Sound.as:347] +PASSED: typeof(s.duration) == "number" [./Sound.as:348] +PASSED: typeof(s.getDuration()) == "number" [./Sound.as:349] +Waiting 3 seconds for onSoundComplete to be called +onSoundComplete called +PASSED: onSoundComplete called +FAILED: expected: 209 obtained: 0 [./Sound.as:296] +PASSED: s.onLoadCalled [./Sound.as:300] +PASSED: typeof(s.onLoadArg) == 'boolean' [./Sound.as:301] +PASSED: s.onLoadArg == true [./Sound.as:302] +PASSED: mp3 over one minute long loaded +#passed: 92 +#failed: 20 +#total tests run: 112 diff --git a/tests/tests/swfs/from_gnash/actionscript.all/Sound-v8/test.toml b/tests/tests/swfs/from_gnash/actionscript.all/Sound-v8/test.toml index a276e4d210272..f45c9f3797fe7 100644 --- a/tests/tests/swfs/from_gnash/actionscript.all/Sound-v8/test.toml +++ b/tests/tests/swfs/from_gnash/actionscript.all/Sound-v8/test.toml @@ -1,2 +1,5 @@ num_ticks = 50 known_failure = true + +[player_options] +with_audio = true