Skip to content

Commit 9bce6b7

Browse files
committed
core: Queue up Sound and SoundChannel methods during loading
Flash supports calling `Sound.play`, `SoundChannel.stop`, and `SoundChannel.soundTransform` while a sound load is in progress (e.g. immediately after calling `Sound.load`). To support this, we queue up information inside `SoundObject` and `SoundChannelObject` when a load is in progress. When a load completes, we trigger any queued `Sound.play` and `SoundChannel.stop` calls, and apply the most recent `SoundChannel.soundTransform`
1 parent 51fe1e9 commit 9bce6b7

File tree

6 files changed

+285
-112
lines changed

6 files changed

+285
-112
lines changed

core/src/avm2/globals/flash/media/sound.rs

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use crate::avm2::activation::Activation;
44
use crate::avm2::class::{Class, ClassAttributes};
55
use crate::avm2::method::{Method, NativeMethodImpl};
6-
use crate::avm2::object::{sound_allocator, Object, SoundChannelObject, TObject};
6+
use crate::avm2::object::{sound_allocator, Object, QueuedPlay, SoundChannelObject, TObject};
77
use crate::avm2::value::Value;
88
use crate::avm2::Error;
99
use crate::avm2::Multiname;
@@ -12,20 +12,24 @@ use crate::avm2::QName;
1212
use crate::backend::navigator::Request;
1313
use crate::character::Character;
1414
use crate::display_object::SoundTransform;
15-
use crate::{avm2_stub_getter, avm2_stub_method};
15+
use crate::{avm2_stub_constructor, avm2_stub_getter, avm2_stub_method};
1616
use gc_arena::{GcCell, MutationContext};
1717
use swf::{SoundEvent, SoundInfo};
1818

1919
/// Implements `flash.media.Sound`'s instance constructor.
2020
pub fn instance_init<'gc>(
2121
activation: &mut Activation<'_, 'gc>,
2222
this: Option<Object<'gc>>,
23-
_args: &[Value<'gc>],
23+
args: &[Value<'gc>],
2424
) -> Result<Value<'gc>, Error<'gc>> {
2525
if let Some(this) = this {
2626
activation.super_init(this, &[])?;
2727

28-
if this.as_sound().is_none() {
28+
if !args.is_empty() {
29+
avm2_stub_constructor!(activation, "flash.media.Sound", "with arguments");
30+
}
31+
32+
if let Some(sound_object) = this.as_sound_object() {
2933
let class_object = this
3034
.instance_of()
3135
.ok_or("Attempted to construct Sound on a bare object.")?;
@@ -42,7 +46,8 @@ pub fn instance_init<'gc>(
4246
.library_for_movie_mut(movie)
4347
.character_by_id(symbol)
4448
{
45-
this.set_sound(activation.context.gc_context, *sound);
49+
let sound = *sound;
50+
sound_object.set_sound(&mut activation.context, sound)?;
4651
} else {
4752
tracing::warn!("Attempted to construct subclass of Sound, {}, which is associated with non-Sound character {}", class_object.inner_class_definition().read().name().local_name(), symbol);
4853
}
@@ -68,10 +73,13 @@ pub fn bytes_total<'gc>(
6873
this: Option<Object<'gc>>,
6974
_args: &[Value<'gc>],
7075
) -> Result<Value<'gc>, Error<'gc>> {
71-
if let Some(sound) = this.and_then(|this| this.as_sound()) {
72-
if let Some(length) = activation.context.audio.get_sound_size(sound) {
73-
return Ok((length).into());
76+
if let Some(sound) = this.and_then(|this| this.as_sound_object()) {
77+
if let Some(sound_handle) = sound.sound_handle() {
78+
if let Some(length) = activation.context.audio.get_sound_size(sound_handle) {
79+
return Ok((length).into());
80+
}
7481
}
82+
return Ok(0.into());
7583
}
7684

7785
Ok(Value::Undefined)
@@ -105,10 +113,13 @@ pub fn length<'gc>(
105113
this: Option<Object<'gc>>,
106114
_args: &[Value<'gc>],
107115
) -> Result<Value<'gc>, Error<'gc>> {
108-
if let Some(sound) = this.and_then(|this| this.as_sound()) {
109-
if let Some(duration) = activation.context.audio.get_sound_duration(sound) {
110-
return Ok((duration).into());
116+
if let Some(sound) = this.and_then(|this| this.as_sound_object()) {
117+
if let Some(sound_handle) = sound.sound_handle() {
118+
if let Some(duration) = activation.context.audio.get_sound_duration(sound_handle) {
119+
return Ok((duration).into());
120+
}
111121
}
122+
return Ok(0.into());
112123
}
113124

114125
Ok(Value::Undefined)
@@ -120,7 +131,7 @@ pub fn play<'gc>(
120131
this: Option<Object<'gc>>,
121132
args: &[Value<'gc>],
122133
) -> Result<Value<'gc>, Error<'gc>> {
123-
if let Some(sound) = this.and_then(|this| this.as_sound()) {
134+
if let Some(sound_object) = this.and_then(|this| this.as_sound_object()) {
124135
let position = args
125136
.get(0)
126137
.cloned()
@@ -133,12 +144,6 @@ pub fn play<'gc>(
133144
.coerce_to_i32(activation)?;
134145
let sound_transform = args.get(2).cloned().unwrap_or(Value::Null).as_object();
135146

136-
if let Some(duration) = activation.context.audio.get_sound_duration(sound) {
137-
if position > duration {
138-
return Ok(Value::Null);
139-
}
140-
}
141-
142147
let in_sample = if position > 0.0 {
143148
Some((position / 1000.0 * 44100.0) as u32)
144149
} else {
@@ -153,23 +158,29 @@ pub fn play<'gc>(
153158
envelope: None,
154159
};
155160

156-
if let Some(instance) = activation
157-
.context
158-
.start_sound(sound, &sound_info, None, None)
159-
{
160-
if let Some(sound_transform) = sound_transform {
161-
let st = SoundTransform::from_avm2_object(activation, sound_transform)?;
162-
activation.context.set_local_sound_transform(instance, st);
163-
}
164-
165-
let sound_channel = SoundChannelObject::from_sound_instance(activation, instance)?;
161+
let sound_transform = if let Some(sound_transform) = sound_transform {
162+
Some(SoundTransform::from_avm2_object(
163+
activation,
164+
sound_transform,
165+
)?)
166+
} else {
167+
None
168+
};
166169

167-
activation
168-
.context
169-
.attach_avm2_sound_channel(instance, sound_channel);
170+
let sound_channel = SoundChannelObject::empty(activation)?;
170171

172+
let queued_play = QueuedPlay {
173+
position,
174+
sound_info,
175+
sound_transform,
176+
sound_channel,
177+
};
178+
if sound_object.play(queued_play, activation)? {
171179
return Ok(sound_channel.into());
172180
}
181+
// If we start playing a loaded sound with an invalid position,
182+
// this method returns `null`
183+
return Ok(Value::Null);
173184
}
174185

175186
Ok(Value::Null)
@@ -202,6 +213,7 @@ pub fn load<'gc>(
202213
args: &[Value<'gc>],
203214
) -> Result<Value<'gc>, Error<'gc>> {
204215
if let Some(this) = this {
216+
// FIXME - don't allow replacing an existing sound
205217
let url_request = match args.get(0) {
206218
Some(Value::Object(request)) => request,
207219
// This should never actually happen

core/src/avm2/globals/flash/media/soundchannel.rs

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,7 @@ pub fn sound_transform<'gc>(
8989
_args: &[Value<'gc>],
9090
) -> Result<Value<'gc>, Error<'gc>> {
9191
if let Some(channel) = this.and_then(|this| this.as_sound_channel()) {
92-
let dobj_st = channel
93-
.instance()
94-
.and_then(|instance| activation.context.local_sound_transform(instance))
95-
.cloned()
96-
.unwrap_or_default();
92+
let dobj_st = channel.sound_transform(activation).unwrap_or_default();
9793

9894
return Ok(dobj_st.into_avm2_object(activation)?.into());
9995
}
@@ -107,20 +103,15 @@ pub fn set_sound_transform<'gc>(
107103
this: Option<Object<'gc>>,
108104
args: &[Value<'gc>],
109105
) -> Result<Value<'gc>, Error<'gc>> {
110-
if let Some(instance) = this
111-
.and_then(|this| this.as_sound_channel())
112-
.and_then(|channel| channel.instance())
113-
{
106+
if let Some(sound_channel) = this.and_then(|this| this.as_sound_channel()) {
114107
let as3_st = args
115108
.get(0)
116109
.cloned()
117110
.unwrap_or(Value::Undefined)
118111
.coerce_to_object(activation)?;
119112
let dobj_st = SoundTransform::from_avm2_object(activation, as3_st)?;
120113

121-
activation
122-
.context
123-
.set_local_sound_transform(instance, dobj_st);
114+
sound_channel.set_sound_transform(activation, dobj_st);
124115
}
125116

126117
Ok(Value::Undefined)
@@ -132,11 +123,8 @@ pub fn stop<'gc>(
132123
this: Option<Object<'gc>>,
133124
_args: &[Value<'gc>],
134125
) -> Result<Value<'gc>, Error<'gc>> {
135-
if let Some(instance) = this
136-
.and_then(|this| this.as_sound_channel())
137-
.and_then(|channel| channel.instance())
138-
{
139-
activation.context.stop_sound(instance);
126+
if let Some(sound_channel) = this.and_then(|this| this.as_sound_channel()) {
127+
sound_channel.stop(activation);
140128
}
141129

142130
Ok(Value::Undefined)

core/src/avm2/object.rs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use crate::avm2::Error;
1717
use crate::avm2::Multiname;
1818
use crate::avm2::Namespace;
1919
use crate::avm2::QName;
20-
use crate::backend::audio::{SoundHandle, SoundInstanceHandle};
2120
use crate::bitmap::bitmap_data::{BitmapData, BitmapDataWrapper};
2221
use crate::display_object::DisplayObject;
2322
use crate::html::TextFormat;
@@ -81,7 +80,7 @@ pub use crate::avm2::object::proxy_object::{proxy_allocator, ProxyObject};
8180
pub use crate::avm2::object::qname_object::{qname_allocator, QNameObject};
8281
pub use crate::avm2::object::regexp_object::{regexp_allocator, RegExpObject};
8382
pub use crate::avm2::object::script_object::{ScriptObject, ScriptObjectData};
84-
pub use crate::avm2::object::sound_object::{sound_allocator, SoundObject};
83+
pub use crate::avm2::object::sound_object::{sound_allocator, QueuedPlay, SoundData, SoundObject};
8584
pub use crate::avm2::object::soundchannel_object::{soundchannel_allocator, SoundChannelObject};
8685
pub use crate::avm2::object::stage3d_object::{stage_3d_allocator, Stage3DObject};
8786
pub use crate::avm2::object::stage_object::{stage_allocator, StageObject};
@@ -1087,25 +1086,15 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
10871086
}
10881087

10891088
/// Unwrap this object's sound handle.
1090-
fn as_sound(self) -> Option<SoundHandle> {
1089+
fn as_sound_object(self) -> Option<SoundObject<'gc>> {
10911090
None
10921091
}
10931092

1094-
/// Associate the object with a particular sound handle.
1095-
///
1096-
/// This does nothing if the object is not a sound.
1097-
fn set_sound(self, _mc: MutationContext<'gc, '_>, _sound: SoundHandle) {}
1098-
10991093
/// Unwrap this object's sound instance handle.
11001094
fn as_sound_channel(self) -> Option<SoundChannelObject<'gc>> {
11011095
None
11021096
}
11031097

1104-
/// Associate the object with a particular sound instance handle.
1105-
///
1106-
/// This does nothing if the object is not a sound channel.
1107-
fn set_sound_instance(self, _mc: MutationContext<'gc, '_>, _sound: SoundInstanceHandle) {}
1108-
11091098
/// Unwrap this object's bitmap data
11101099
fn as_bitmap_data(&self) -> Option<GcCell<'gc, BitmapData<'gc>>> {
11111100
None

0 commit comments

Comments
 (0)