avm2: Implement Sound.load

This commit is contained in:
Mike Welsh 2022-07-30 18:42:47 -07:00
parent 4a04923d04
commit fe46d5046a
3 changed files with 162 additions and 16 deletions

View File

@ -251,7 +251,7 @@ fn load_sound<'gc>(
.get(1)
.unwrap_or(&Value::Undefined)
.as_bool(activation.swf_version());
let future = activation.context.load_manager.load_sound(
let future = activation.context.load_manager.load_sound_avm1(
activation.context.player.clone(),
sound,
Request::get(url.to_utf8_lossy().into_owned()),

View File

@ -8,6 +8,7 @@ use crate::avm2::value::Value;
use crate::avm2::Error;
use crate::avm2::Namespace;
use crate::avm2::QName;
use crate::backend::navigator::Request;
use crate::character::Character;
use crate::display_object::SoundTransform;
use gc_arena::{GcCell, MutationContext};
@ -190,11 +191,33 @@ pub fn close<'gc>(
/// Stubs `Sound.load`
pub fn load<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Err("Sound.load is a stub.".into())
if let Some(this) = this {
let url_request = match args.get(0) {
Some(Value::Object(request)) => request,
// This should never actually happen
_ => return Ok(Value::Undefined),
};
let url = url_request
.get_property(&QName::dynamic_name("url").into(), activation)?
.coerce_to_string(activation)?;
// TODO: context parameter currently unused.
let _sound_context = args.get(1);
let future = activation.context.load_manager.load_sound_avm2(
activation.context.player.clone(),
this,
// FIXME: Set options from the `URLRequest`.
Request::get(url.to_string()),
);
activation.context.navigator.spawn_future(future);
}
Ok(Value::Undefined)
}
/// Stubs `Sound.loadCompressedDataFromByteArray`

View File

@ -120,6 +120,9 @@ pub enum Error {
#[error("Non-data loader spawned as data loader")]
NotLoadDataLoader,
#[error("Non-sound loader spawned as sound loader")]
NotSoundLoader,
#[error("Could not fetch: {0}")]
FetchError(String),
@ -181,8 +184,9 @@ impl<'gc> LoadManager<'gc> {
| Loader::Movie { self_handle, .. }
| Loader::Form { self_handle, .. }
| Loader::LoadVars { self_handle, .. }
| Loader::LoadURLLoader { self_handle, .. } => *self_handle = Some(handle),
Loader::Sound { self_handle, .. } => *self_handle = Some(handle),
| Loader::LoadURLLoader { self_handle, .. }
| Loader::SoundAvm1 { self_handle, .. }
| Loader::SoundAvm2 { self_handle, .. } => *self_handle = Some(handle),
}
handle
}
@ -311,23 +315,41 @@ impl<'gc> LoadManager<'gc> {
loader.load_url_loader(player, request, data_format)
}
/// Kick off an audio load.
/// Kick off an AVM1 audio load.
///
/// Returns the loader's async process, which you will need to spawn.
pub fn load_sound(
pub fn load_sound_avm1(
&mut self,
player: Weak<Mutex<Player>>,
target_object: SoundObject<'gc>,
request: Request,
is_streaming: bool,
) -> OwnedFuture<(), Error> {
let loader = Loader::Sound {
let loader = Loader::SoundAvm1 {
self_handle: None,
target_object,
};
let handle = self.add_loader(loader);
let loader = self.get_loader_mut(handle).unwrap();
loader.sound_loader(player, request, is_streaming)
loader.sound_loader_avm1(player, request, is_streaming)
}
/// Kick off an AVM2 audio load.
///
/// Returns the loader's async process, which you will need to spawn.
pub fn load_sound_avm2(
&mut self,
player: Weak<Mutex<Player>>,
target_object: Avm2Object<'gc>,
request: Request,
) -> OwnedFuture<(), Error> {
let loader = Loader::SoundAvm2 {
self_handle: None,
target_object,
};
let handle = self.add_loader(loader);
let loader = self.get_loader_mut(handle).unwrap();
loader.sound_loader_avm2(player, request)
}
}
@ -423,7 +445,7 @@ pub enum Loader<'gc> {
},
/// Loader that is loading an MP3 into an AVM1 Sound object.
Sound {
SoundAvm1 {
/// The handle to refer to this loader instance.
#[collect(require_static)]
self_handle: Option<Handle>,
@ -431,6 +453,16 @@ pub enum Loader<'gc> {
/// The target AVM1 object to load the audio into.
target_object: SoundObject<'gc>,
},
/// Loader that is loading an MP3 into an AVM2 Sound object.
SoundAvm2 {
/// The handle to refer to this loader instance.
#[collect(require_static)]
self_handle: Option<Handle>,
/// The target AVM1 object to load the audio into.
target_object: Avm2Object<'gc>,
},
}
impl<'gc> Loader<'gc> {
@ -948,14 +980,16 @@ impl<'gc> Loader<'gc> {
}
/// Creates a future for a Sound load call.
fn sound_loader(
fn sound_loader_avm1(
&mut self,
player: Weak<Mutex<Player>>,
request: Request,
is_streaming: bool,
) -> OwnedFuture<(), Error> {
let handle = match self {
Loader::Sound { self_handle, .. } => self_handle.expect("Loader not self-introduced"),
Loader::SoundAvm1 { self_handle, .. } => {
self_handle.expect("Loader not self-introduced")
}
_ => return Box::pin(async { Err(Error::NotLoadVarsLoader) }),
};
@ -971,9 +1005,9 @@ impl<'gc> Loader<'gc> {
player.lock().unwrap().update(|uc| {
let loader = uc.load_manager.get_loader(handle);
let sound_object = match loader {
Some(&Loader::Sound { target_object, .. }) => target_object,
Some(&Loader::SoundAvm1 { target_object, .. }) => target_object,
None => return Err(Error::Cancelled),
_ => return Err(Error::NotLoadVarsLoader),
_ => return Err(Error::NotSoundLoader),
};
let success = data
@ -1008,6 +1042,95 @@ impl<'gc> Loader<'gc> {
})
}
/// Creates a future for a LoadURLLoader load call.
fn sound_loader_avm2(
&mut self,
player: Weak<Mutex<Player>>,
request: Request,
) -> OwnedFuture<(), Error> {
let handle = match self {
Loader::SoundAvm2 { self_handle, .. } => {
self_handle.expect("Loader not self-introduced")
}
_ => return Box::pin(async { Err(Error::NotLoadDataLoader) }),
};
let player = player
.upgrade()
.expect("Could not upgrade weak reference to player");
Box::pin(async move {
let fetch = player.lock().unwrap().navigator().fetch(request);
let response = fetch.await;
player.lock().unwrap().update(|uc| {
let loader = uc.load_manager.get_loader(handle);
let sound_object = match loader {
Some(&Loader::SoundAvm2 { target_object, .. }) => target_object,
None => return Err(Error::Cancelled),
_ => return Err(Error::NotSoundLoader),
};
match response {
Ok(response) => {
let handle = uc.audio.register_mp3(&response.body)?;
sound_object.set_sound(uc.gc_context, handle);
// FIXME - the "open" event should be fired earlier, and not fired in case of ioerror.
let mut activation = Avm2Activation::from_nothing(uc.reborrow());
let open_evt =
Avm2EventObject::bare_default_event(&mut activation.context, "open");
if let Err(e) =
Avm2::dispatch_event(&mut activation.context, open_evt, sound_object)
{
log::error!(
"Encountered AVM2 error when broadcasting `open` event: {}",
e
);
}
let complete_evt = Avm2EventObject::bare_default_event(
&mut activation.context,
"complete",
);
if let Err(e) = Avm2::dispatch_event(uc, complete_evt, sound_object) {
log::error!(
"Encountered AVM2 error when broadcasting `complete` event: {}",
e
);
}
}
Err(_err) => {
// FIXME: Match the exact error message generated by Flash.
let mut activation = Avm2Activation::from_nothing(uc.reborrow());
let io_error_evt_cls = activation.avm2().classes().ioerrorevent;
let io_error_evt = io_error_evt_cls
.construct(
&mut activation,
&[
"ioError".into(),
false.into(),
false.into(),
"Error #2032: Stream Error".into(),
2032.into(),
],
)
.map_err(|e| Error::Avm2Error(e.to_string()))?;
if let Err(e) = Avm2::dispatch_event(uc, io_error_evt, sound_object) {
log::error!(
"Encountered AVM2 error when broadcasting `ioError` event: {}",
e
);
}
}
}
Ok(())
})
})
}
/// Report a movie loader start event to script code.
fn movie_loader_start(handle: Index, uc: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let me = uc.load_manager.get_loader_mut(handle);