Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/common/src/avm_string/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ ruffle_macros::define_common_strings! {
"rollOut",
"rollOver",
"rr",
"sampleData",
"save",
"Selection",
"separatorBefore",
Expand Down
2 changes: 1 addition & 1 deletion core/src/avm2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pub use crate::avm2::multiname::Multiname;
pub use crate::avm2::namespace::{CommonNamespaces, Namespace};
pub use crate::avm2::object::{
ArrayObject, BitmapDataObject, ClassObject, EventObject, LoaderInfoObject, Object,
SharedObjectObject, SoundChannelObject, StageObject, TObject,
SharedObjectObject, SoundChannelObject, SoundObject, StageObject, TObject,
};
pub use crate::avm2::qname::QName;
pub use crate::avm2::value::Value;
Expand Down
2 changes: 2 additions & 0 deletions core/src/avm2/globals/flash/events/SampleDataEvent.as
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package flash.events {
public static const SAMPLE_DATA:String = "sampleData";

public var _position:Number;

[Ruffle(NativeAccessible)]
public var _data:ByteArray;

public function SampleDataEvent(
Expand Down
28 changes: 28 additions & 0 deletions core/src/avm2/globals/flash/media/sound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,33 @@
None
};

// If no load has been initiated yet, this is a generated (synthesized) sound.
// Register it with the audio backend so it can receive SampleDataEvent callbacks.
if sound_object.is_empty() {
let sound_channel = SoundChannelObject::empty(activation);
let handle = activation
.context
.audio_manager
.start_generated_sound(activation.context.audio, sound_object)
.expect("not too many sounds");
sound_channel.set_sound_instance(activation.context, handle);
activation
.context
.audio_manager
.attach_avm2_sound_channel(handle, sound_channel);

Check warning on line 190 in core/src/avm2/globals/flash/media/sound.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (180–190)
// Transition state from Empty → Generated
sound_object.play(
QueuedPlay {
position,
sound_info,
sound_transform,
sound_channel,
},
activation,

Check warning on line 199 in core/src/avm2/globals/flash/media/sound.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (192–199)
);
return Ok(sound_channel.into());

Check warning on line 201 in core/src/avm2/globals/flash/media/sound.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (201)
}

let sound_channel = SoundChannelObject::empty(activation);

let queued_play = QueuedPlay {
Expand Down Expand Up @@ -261,6 +288,7 @@
Request::get(url.to_string()),
);
activation.context.navigator.spawn_future(future);
this.load_called(activation.context);
this.set_loading_state(SoundLoadingState::Loading);

Ok(Value::Undefined)
Expand Down
27 changes: 26 additions & 1 deletion core/src/avm2/object/event_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

use crate::avm2::Error;
use crate::avm2::activation::Activation;
use crate::avm2::bytearray::ByteArrayStorage;
use crate::avm2::events::Event;
use crate::avm2::object::script_object::ScriptObjectData;
use crate::avm2::object::{ClassObject, Object, ScriptObject, TObject};
use crate::avm2::object::{ByteArrayObject, ClassObject, Object, ScriptObject, TObject};
use crate::avm2::value::Value;
use crate::context::UpdateContext;
use crate::display_object::TDisplayObject;
Expand Down Expand Up @@ -227,6 +228,30 @@ impl<'gc> EventObject<'gc> {
)
}

pub fn sample_data_event(
activation: &mut Activation<'_, 'gc>,
position: u32,
) -> EventObject<'gc> {
let storage = ByteArrayStorage::new(activation.context);
let data = ByteArrayObject::from_storage(activation.context, storage);

let event_name = istr!("sampleData");
let sample_data_event_cls = activation.avm2().classes().sampledataevent;
Self::from_class_and_args(
activation,
sample_data_event_cls,
&[
event_name.into(),
//bubbles
false.into(),
//cancelable
false.into(),
position.into(),
data.into(),
],
)
}

pub fn net_status_event<'a>(
activation: &mut Activation<'_, 'gc>,
info: impl IntoIterator<Item = (&'a str, &'a str)>,
Expand Down
64 changes: 55 additions & 9 deletions core/src/avm2/object/sound_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ pub fn sound_allocator<'gc>(
SoundObjectData {
base,
loading_state: Cell::new(SoundLoadingState::New),
sound_data: RefLock::new(SoundData::NotLoaded {
queued_plays: Vec::new(),
}),
sound_data: RefLock::new(SoundData::Empty),
id3: Lock::new(None),
},
))
Expand Down Expand Up @@ -95,13 +93,17 @@ pub struct SoundObjectData<'gc> {
#[derive(Collect)]
#[collect(no_drop)]
pub enum SoundData<'gc> {
NotLoaded {
queued_plays: Vec<QueuedPlay<'gc>>,
},
/// Initial state: no load or play called yet.
Empty,
/// `load()` was called; waiting for data.
Loading { queued_plays: Vec<QueuedPlay<'gc>> },
Loaded {
#[collect(require_static)]
sound: SoundHandle,
},
/// `play()` was called on an empty sound (no load initiated);
/// audio data comes from `SampleDataEvent` dispatches.
Generated,
}

#[derive(Clone, Collect)]
Expand All @@ -126,8 +128,8 @@ impl<'gc> SoundObject<'gc> {
pub fn sound_handle(self) -> Option<SoundHandle> {
let sound_data = self.0.sound_data.borrow();
match &*sound_data {
SoundData::NotLoaded { .. } => None,
SoundData::Loaded { sound } => Some(*sound),
_ => None,
}
}

Expand All @@ -148,7 +150,13 @@ impl<'gc> SoundObject<'gc> {
)
.borrow_mut();
match &mut *sound_data {
SoundData::NotLoaded { queued_plays } => {
SoundData::Empty => {
// play() was called before load() — this becomes a generated sound.
*sound_data = SoundData::Generated;
// We don't know the length yet, so return the `SoundChannel`
true
}
SoundData::Loading { queued_plays } => {
// Avoid to enqueue more unloaded sounds than the maximum allowed to be played
if queued_plays.len() >= AudioManager::MAX_SOUNDS {
tracing::warn!("Sound.play: too many unloaded sounds queued");
Expand All @@ -161,6 +169,38 @@ impl<'gc> SoundObject<'gc> {
true
}
SoundData::Loaded { sound } => play_queued(queued, *sound, activation.context),
SoundData::Generated => {
// Already generated, return a channel
true
}
}
}

/// Returns `true` if this sound is in the `Empty` state
/// (no `load()` call has been made yet).
pub fn is_empty(self) -> bool {
matches!(&*self.0.sound_data.borrow(), SoundData::Empty)
}

/// Transitions from `Empty` → `Loading`. Called when `Sound.load()` is invoked.
pub fn load_called(self, context: &mut UpdateContext<'gc>) {
let mut sound_data =
unlock!(Gc::write(context.gc(), self.0), SoundObjectData, sound_data).borrow_mut();
match &*sound_data {
SoundData::Empty => {
*sound_data = SoundData::Loading {
queued_plays: Vec::new(),
};
}
SoundData::Loading { .. } => {
panic!("Tried to load sound that is already Loading");
}
SoundData::Loaded { .. } => {
panic!("Tried to load sound that is already Loaded");
}
SoundData::Generated => {
panic!("Tried to load sound that is already Generated");
}
}
}

Expand All @@ -169,7 +209,10 @@ impl<'gc> SoundObject<'gc> {
unlock!(Gc::write(context.gc(), self.0), SoundObjectData, sound_data).borrow_mut();

match &mut *sound_data {
SoundData::NotLoaded { queued_plays } => {
SoundData::Empty => {
*sound_data = SoundData::Loaded { sound };
}
SoundData::Loading { queued_plays } => {
for queued in std::mem::take(queued_plays) {
play_queued(queued, sound, context);
}
Expand All @@ -178,6 +221,9 @@ impl<'gc> SoundObject<'gc> {
SoundData::Loaded { sound: old_sound } => {
panic!("Tried to replace sound {old_sound:?} with {sound:?}")
}
SoundData::Generated => {
panic!("Tried to replace generated sound with {sound:?}")
}
}
self.set_loading_state(SoundLoadingState::Loaded);
}
Expand Down
Loading
Loading