avm2: Implement Sound.load
This commit is contained in:
parent
4a04923d04
commit
fe46d5046a
|
@ -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()),
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue