avm1: Store movie URL on load and implement _url (merge #912)
This commit is contained in:
commit
8ac2ad9b40
|
@ -1246,6 +1246,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
|
||||||
context.player.clone().unwrap(),
|
context.player.clone().unwrap(),
|
||||||
level,
|
level,
|
||||||
fetch,
|
fetch,
|
||||||
|
url,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
context.navigator.spawn_future(process);
|
context.navigator.spawn_future(process);
|
||||||
|
@ -1336,6 +1337,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
|
||||||
context.player.clone().unwrap(),
|
context.player.clone().unwrap(),
|
||||||
clip_target,
|
clip_target,
|
||||||
fetch,
|
fetch,
|
||||||
|
url.to_string(),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
context.navigator.spawn_future(process);
|
context.navigator.spawn_future(process);
|
||||||
|
@ -1353,6 +1355,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
|
||||||
context.player.clone().unwrap(),
|
context.player.clone().unwrap(),
|
||||||
level,
|
level,
|
||||||
fetch,
|
fetch,
|
||||||
|
url.to_string(),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
context.navigator.spawn_future(process);
|
context.navigator.spawn_future(process);
|
||||||
|
|
|
@ -1096,6 +1096,7 @@ fn load_movie<'gc>(
|
||||||
context.player.clone().unwrap(),
|
context.player.clone().unwrap(),
|
||||||
DisplayObject::MovieClip(target),
|
DisplayObject::MovieClip(target),
|
||||||
fetch,
|
fetch,
|
||||||
|
url.to_string(),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,7 @@ pub fn load_clip<'gc>(
|
||||||
context.player.clone().unwrap(),
|
context.player.clone().unwrap(),
|
||||||
DisplayObject::MovieClip(movieclip),
|
DisplayObject::MovieClip(movieclip),
|
||||||
fetch,
|
fetch,
|
||||||
|
url.to_string(),
|
||||||
Some(this),
|
Some(this),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -898,11 +898,15 @@ fn drop_target<'gc>(
|
||||||
|
|
||||||
fn url<'gc>(
|
fn url<'gc>(
|
||||||
_activation: &mut Activation<'_, 'gc>,
|
_activation: &mut Activation<'_, 'gc>,
|
||||||
_context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
_this: DisplayObject<'gc>,
|
this: DisplayObject<'gc>,
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
log::warn!("Unimplemented property _url");
|
Ok(this
|
||||||
Ok("".into())
|
.as_movie_clip()
|
||||||
|
.and_then(|mc| mc.movie())
|
||||||
|
.and_then(|mov| mov.url().map(|s| s.to_string()))
|
||||||
|
.map(|s| AvmString::new(context.gc_context, s).into())
|
||||||
|
.unwrap_or_else(|| "".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn high_quality<'gc>(
|
fn high_quality<'gc>(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! Browser-related platform functions
|
//! Browser-related platform functions
|
||||||
|
|
||||||
use crate::loader::Error;
|
use crate::loader::Error;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
@ -11,6 +12,57 @@ use std::sync::mpsc::{channel, Receiver, Sender};
|
||||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use swf::avm1::types::SendVarsMethod;
|
use swf::avm1::types::SendVarsMethod;
|
||||||
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
|
/// Attempt to convert a relative filesystem path into an absolute `file:///`
|
||||||
|
/// URL.
|
||||||
|
///
|
||||||
|
/// If the relative path is an absolute path, the base will not be used, but it
|
||||||
|
/// will still be parsed into a `Url`.
|
||||||
|
///
|
||||||
|
/// This is the desktop version of this function, which actually carries out
|
||||||
|
/// the above instructions. On non-Unix, non-Windows, non-Redox environments,
|
||||||
|
/// this function always yields an error.
|
||||||
|
#[cfg(any(unix, windows, target_os = "redox"))]
|
||||||
|
pub fn url_from_relative_path<P: AsRef<Path>>(base: P, relative: &str) -> Result<Url, ParseError> {
|
||||||
|
let parsed = Url::from_file_path(relative);
|
||||||
|
if let Err(()) = parsed {
|
||||||
|
let base =
|
||||||
|
Url::from_directory_path(base).map_err(|_| ParseError::RelativeUrlWithoutBase)?;
|
||||||
|
|
||||||
|
return base.join(relative);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(parsed.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to convert a relative filesystem path into an absolute `file:///`
|
||||||
|
/// URL.
|
||||||
|
///
|
||||||
|
/// If the relative path is an absolute path, the base will not be used, but it
|
||||||
|
/// will still be parsed into a `Url`.
|
||||||
|
///
|
||||||
|
/// This is the web version of this function, which always yields an error. On
|
||||||
|
/// Unix, Windows, or Redox, this function actually carries out the above
|
||||||
|
/// instructions.
|
||||||
|
#[cfg(not(any(unix, windows, target_os = "redox")))]
|
||||||
|
pub fn url_from_relative_path<P: AsRef<Path>>(base: P, relative: &str) -> Result<Url, ParseError> {
|
||||||
|
Err(ParseError::RelativeUrlWithoutBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to convert a relative URL into an absolute URL, using the base URL
|
||||||
|
/// if necessary.
|
||||||
|
///
|
||||||
|
/// If the relative URL is actually absolute, then the base will not be used.
|
||||||
|
pub fn url_from_relative_url(base: &str, relative: &str) -> Result<Url, ParseError> {
|
||||||
|
let parsed = Url::parse(relative);
|
||||||
|
if let Err(ParseError::RelativeUrlWithoutBase) = parsed {
|
||||||
|
let base = Url::parse(base)?;
|
||||||
|
return base.join(relative);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed
|
||||||
|
}
|
||||||
|
|
||||||
/// Enumerates all possible navigation methods.
|
/// Enumerates all possible navigation methods.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
@ -132,6 +184,16 @@ pub trait NavigatorBackend {
|
||||||
/// TODO: For some reason, `wasm_bindgen_futures` wants unpinnable futures.
|
/// TODO: For some reason, `wasm_bindgen_futures` wants unpinnable futures.
|
||||||
/// This seems highly limiting.
|
/// This seems highly limiting.
|
||||||
fn spawn_future(&mut self, future: OwnedFuture<(), Error>);
|
fn spawn_future(&mut self, future: OwnedFuture<(), Error>);
|
||||||
|
|
||||||
|
/// Resolve a relative URL.
|
||||||
|
///
|
||||||
|
/// This function must not change URLs which are already protocol, domain,
|
||||||
|
/// and path absolute. For URLs that are relative, the implementator of
|
||||||
|
/// this function may opt to convert them to absolute using an implementor
|
||||||
|
/// defined base. For a web browser, the most obvious base would be the
|
||||||
|
/// current document's base URL, while the most obvious base for a desktop
|
||||||
|
/// client would be the file-URL form of the current path.
|
||||||
|
fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A null implementation of an event loop that only supports blocking.
|
/// A null implementation of an event loop that only supports blocking.
|
||||||
|
@ -303,4 +365,13 @@ impl NavigatorBackend for NullNavigatorBackend {
|
||||||
channel.send(future).unwrap();
|
channel.send(future).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str> {
|
||||||
|
let relative = url_from_relative_path(&self.relative_base_path, url);
|
||||||
|
if let Ok(relative) = relative {
|
||||||
|
relative.into_string().into()
|
||||||
|
} else {
|
||||||
|
url.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,8 @@ impl<'gc> Button<'gc> {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut actions = vec![];
|
let mut actions = vec![];
|
||||||
for action in &button.actions {
|
for action in &button.actions {
|
||||||
let action_data = source_movie.owned_subslice(action.action_data.clone());
|
let action_data =
|
||||||
|
source_movie.owned_subslice(action.action_data.clone(), &source_movie.movie);
|
||||||
for condition in &action.conditions {
|
for condition in &action.conditions {
|
||||||
let button_action = ButtonAction {
|
let button_action = ButtonAction {
|
||||||
action_data: action_data.clone(),
|
action_data: action_data.clone(),
|
||||||
|
|
|
@ -2567,7 +2567,7 @@ impl ClipAction {
|
||||||
|
|
||||||
let len = other.action_data.len();
|
let len = other.action_data.len();
|
||||||
let key_code = other.key_code;
|
let key_code = other.key_code;
|
||||||
let movie = Arc::new(movie.from_movie_and_subdata(other.action_data));
|
let movie = Arc::new(movie.from_movie_and_subdata(other.action_data, &movie));
|
||||||
other.events.into_iter().map(move |event| Self {
|
other.events.into_iter().map(move |event| Self {
|
||||||
event: match event {
|
event: match event {
|
||||||
ClipEventFlag::Construct => ClipEvent::Construct,
|
ClipEventFlag::Construct => ClipEvent::Construct,
|
||||||
|
|
|
@ -424,6 +424,7 @@ mod tests {
|
||||||
use crate::font::{EvalParameters, Font};
|
use crate::font::{EvalParameters, Font};
|
||||||
use crate::player::{Player, DEVICE_FONT_TAG};
|
use crate::player::{Player, DEVICE_FONT_TAG};
|
||||||
use gc_arena::{rootless_arena, MutationContext};
|
use gc_arena::{rootless_arena, MutationContext};
|
||||||
|
use std::ops::DerefMut;
|
||||||
use swf::Twips;
|
use swf::Twips;
|
||||||
|
|
||||||
fn with_device_font<F>(callback: F)
|
fn with_device_font<F>(callback: F)
|
||||||
|
@ -432,7 +433,8 @@ mod tests {
|
||||||
{
|
{
|
||||||
rootless_arena(|mc| {
|
rootless_arena(|mc| {
|
||||||
let mut renderer: Box<dyn RenderBackend> = Box::new(NullRenderer::new());
|
let mut renderer: Box<dyn RenderBackend> = Box::new(NullRenderer::new());
|
||||||
let device_font = Player::load_device_font(mc, DEVICE_FONT_TAG, &mut renderer).unwrap();
|
let device_font =
|
||||||
|
Player::load_device_font(mc, DEVICE_FONT_TAG, renderer.deref_mut()).unwrap();
|
||||||
|
|
||||||
callback(mc, device_font);
|
callback(mc, device_font);
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,6 +22,9 @@ pub enum Error {
|
||||||
#[error("Load cancelled")]
|
#[error("Load cancelled")]
|
||||||
Cancelled,
|
Cancelled,
|
||||||
|
|
||||||
|
#[error("Non-root-movie loader spawned as root movie loader")]
|
||||||
|
NotRootMovieLoader,
|
||||||
|
|
||||||
#[error("Non-movie loader spawned as movie loader")]
|
#[error("Non-movie loader spawned as movie loader")]
|
||||||
NotMovieLoader,
|
NotMovieLoader,
|
||||||
|
|
||||||
|
@ -34,6 +37,9 @@ pub enum Error {
|
||||||
#[error("Non-XML loader spawned as XML loader")]
|
#[error("Non-XML loader spawned as XML loader")]
|
||||||
NotXmlLoader,
|
NotXmlLoader,
|
||||||
|
|
||||||
|
#[error("Could not fetch movie {0}")]
|
||||||
|
FetchError(String),
|
||||||
|
|
||||||
#[error("Invalid SWF")]
|
#[error("Invalid SWF")]
|
||||||
InvalidSwf(#[from] crate::tag_utils::Error),
|
InvalidSwf(#[from] crate::tag_utils::Error),
|
||||||
|
|
||||||
|
@ -108,6 +114,27 @@ impl<'gc> LoadManager<'gc> {
|
||||||
self.0.get_mut(handle)
|
self.0.get_mut(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Kick off the root movie load.
|
||||||
|
///
|
||||||
|
/// The root movie is special because it determines a few bits of player
|
||||||
|
/// state, such as the size of the stage and the current frame rate. Ergo,
|
||||||
|
/// this method should only be called once, by the player that is trying to
|
||||||
|
/// kick off it's root movie load.
|
||||||
|
pub fn load_root_movie(
|
||||||
|
&mut self,
|
||||||
|
player: Weak<Mutex<Player>>,
|
||||||
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
||||||
|
url: String,
|
||||||
|
) -> OwnedFuture<(), Error> {
|
||||||
|
let loader = Loader::RootMovie { self_handle: None };
|
||||||
|
let handle = self.add_loader(loader);
|
||||||
|
|
||||||
|
let loader = self.get_loader_mut(handle).unwrap();
|
||||||
|
loader.introduce_loader_handle(handle);
|
||||||
|
|
||||||
|
loader.root_movie_loader(player, fetch, url)
|
||||||
|
}
|
||||||
|
|
||||||
/// Kick off a movie clip load.
|
/// Kick off a movie clip load.
|
||||||
///
|
///
|
||||||
/// Returns the loader's async process, which you will need to spawn.
|
/// Returns the loader's async process, which you will need to spawn.
|
||||||
|
@ -116,6 +143,7 @@ impl<'gc> LoadManager<'gc> {
|
||||||
player: Weak<Mutex<Player>>,
|
player: Weak<Mutex<Player>>,
|
||||||
target_clip: DisplayObject<'gc>,
|
target_clip: DisplayObject<'gc>,
|
||||||
fetch: OwnedFuture<Vec<u8>, Error>,
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
||||||
|
url: String,
|
||||||
target_broadcaster: Option<Object<'gc>>,
|
target_broadcaster: Option<Object<'gc>>,
|
||||||
) -> OwnedFuture<(), Error> {
|
) -> OwnedFuture<(), Error> {
|
||||||
let loader = Loader::Movie {
|
let loader = Loader::Movie {
|
||||||
|
@ -129,7 +157,7 @@ impl<'gc> LoadManager<'gc> {
|
||||||
let loader = self.get_loader_mut(handle).unwrap();
|
let loader = self.get_loader_mut(handle).unwrap();
|
||||||
loader.introduce_loader_handle(handle);
|
loader.introduce_loader_handle(handle);
|
||||||
|
|
||||||
loader.movie_loader(player, fetch)
|
loader.movie_loader(player, fetch, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates that a movie clip has initialized (ran it's first frame).
|
/// Indicates that a movie clip has initialized (ran it's first frame).
|
||||||
|
@ -229,6 +257,12 @@ impl<'gc> Default for LoadManager<'gc> {
|
||||||
|
|
||||||
/// A struct that holds garbage-collected pointers for asynchronous code.
|
/// A struct that holds garbage-collected pointers for asynchronous code.
|
||||||
pub enum Loader<'gc> {
|
pub enum Loader<'gc> {
|
||||||
|
/// Loader that is loading the root movie of a player.
|
||||||
|
RootMovie {
|
||||||
|
/// The handle to refer to this loader instance.
|
||||||
|
self_handle: Option<Handle>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Loader that is loading a new movie into a movieclip.
|
/// Loader that is loading a new movie into a movieclip.
|
||||||
Movie {
|
Movie {
|
||||||
/// The handle to refer to this loader instance.
|
/// The handle to refer to this loader instance.
|
||||||
|
@ -291,6 +325,7 @@ pub enum Loader<'gc> {
|
||||||
unsafe impl<'gc> Collect for Loader<'gc> {
|
unsafe impl<'gc> Collect for Loader<'gc> {
|
||||||
fn trace(&self, cc: CollectionContext) {
|
fn trace(&self, cc: CollectionContext) {
|
||||||
match self {
|
match self {
|
||||||
|
Loader::RootMovie { .. } => {}
|
||||||
Loader::Movie {
|
Loader::Movie {
|
||||||
target_clip,
|
target_clip,
|
||||||
target_broadcaster,
|
target_broadcaster,
|
||||||
|
@ -313,6 +348,7 @@ impl<'gc> Loader<'gc> {
|
||||||
/// run.
|
/// run.
|
||||||
pub fn introduce_loader_handle(&mut self, handle: Handle) {
|
pub fn introduce_loader_handle(&mut self, handle: Handle) {
|
||||||
match self {
|
match self {
|
||||||
|
Loader::RootMovie { self_handle, .. } => *self_handle = Some(handle),
|
||||||
Loader::Movie { self_handle, .. } => *self_handle = Some(handle),
|
Loader::Movie { self_handle, .. } => *self_handle = Some(handle),
|
||||||
Loader::Form { self_handle, .. } => *self_handle = Some(handle),
|
Loader::Form { self_handle, .. } => *self_handle = Some(handle),
|
||||||
Loader::LoadVars { self_handle, .. } => *self_handle = Some(handle),
|
Loader::LoadVars { self_handle, .. } => *self_handle = Some(handle),
|
||||||
|
@ -320,6 +356,46 @@ impl<'gc> Loader<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a future for the root movie loader.
|
||||||
|
pub fn root_movie_loader(
|
||||||
|
&mut self,
|
||||||
|
player: Weak<Mutex<Player>>,
|
||||||
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
||||||
|
mut url: String,
|
||||||
|
) -> OwnedFuture<(), Error> {
|
||||||
|
let _handle = match self {
|
||||||
|
Loader::RootMovie { self_handle, .. } => {
|
||||||
|
self_handle.expect("Loader not self-introduced")
|
||||||
|
}
|
||||||
|
_ => return Box::pin(async { Err(Error::NotMovieLoader) }),
|
||||||
|
};
|
||||||
|
|
||||||
|
let player = player
|
||||||
|
.upgrade()
|
||||||
|
.expect("Could not upgrade weak reference to player");
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
player.lock().expect("Could not lock player!!").update(
|
||||||
|
|_avm1, _avm2, uc| -> Result<(), Error> {
|
||||||
|
url = uc.navigator.resolve_relative_url(&url).into_owned();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let data = (fetch.await)
|
||||||
|
.and_then(|data| Ok((data.len(), SwfMovie::from_data(&data, Some(url.clone()))?)));
|
||||||
|
|
||||||
|
if let Ok((_length, movie)) = data {
|
||||||
|
player.lock().unwrap().set_root_movie(Arc::new(movie));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::FetchError(url))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct a future for the given movie loader.
|
/// Construct a future for the given movie loader.
|
||||||
///
|
///
|
||||||
/// The given future should be passed immediately to an executor; it will
|
/// The given future should be passed immediately to an executor; it will
|
||||||
|
@ -331,6 +407,7 @@ impl<'gc> Loader<'gc> {
|
||||||
&mut self,
|
&mut self,
|
||||||
player: Weak<Mutex<Player>>,
|
player: Weak<Mutex<Player>>,
|
||||||
fetch: OwnedFuture<Vec<u8>, Error>,
|
fetch: OwnedFuture<Vec<u8>, Error>,
|
||||||
|
mut url: String,
|
||||||
) -> OwnedFuture<(), Error> {
|
) -> OwnedFuture<(), Error> {
|
||||||
let handle = match self {
|
let handle = match self {
|
||||||
Loader::Movie { self_handle, .. } => self_handle.expect("Loader not self-introduced"),
|
Loader::Movie { self_handle, .. } => self_handle.expect("Loader not self-introduced"),
|
||||||
|
@ -344,6 +421,8 @@ impl<'gc> Loader<'gc> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
player.lock().expect("Could not lock player!!").update(
|
player.lock().expect("Could not lock player!!").update(
|
||||||
|avm1, _avm2, uc| -> Result<(), Error> {
|
|avm1, _avm2, uc| -> Result<(), Error> {
|
||||||
|
url = uc.navigator.resolve_relative_url(&url).into_owned();
|
||||||
|
|
||||||
let (clip, broadcaster) = match uc.load_manager.get_loader(handle) {
|
let (clip, broadcaster) = match uc.load_manager.get_loader(handle) {
|
||||||
Some(Loader::Movie {
|
Some(Loader::Movie {
|
||||||
target_clip,
|
target_clip,
|
||||||
|
@ -375,7 +454,8 @@ impl<'gc> Loader<'gc> {
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let data = (fetch.await).and_then(|data| Ok((data.len(), SwfMovie::from_data(&data)?)));
|
let data = (fetch.await)
|
||||||
|
.and_then(|data| Ok((data.len(), SwfMovie::from_data(&data, Some(url.clone()))?)));
|
||||||
if let Ok((length, movie)) = data {
|
if let Ok((length, movie)) = data {
|
||||||
let movie = Arc::new(movie);
|
let movie = Arc::new(movie);
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,9 @@ use crate::avm1::object::Object;
|
||||||
use crate::avm1::{Avm1, AvmString, TObject, Timers, Value};
|
use crate::avm1::{Avm1, AvmString, TObject, Timers, Value};
|
||||||
use crate::avm2::Avm2;
|
use crate::avm2::Avm2;
|
||||||
use crate::backend::input::{InputBackend, MouseCursor};
|
use crate::backend::input::{InputBackend, MouseCursor};
|
||||||
|
use crate::backend::navigator::{NavigatorBackend, RequestOptions};
|
||||||
use crate::backend::storage::StorageBackend;
|
use crate::backend::storage::StorageBackend;
|
||||||
use crate::backend::{
|
use crate::backend::{audio::AudioBackend, render::Letterbox, render::RenderBackend};
|
||||||
audio::AudioBackend, navigator::NavigatorBackend, render::Letterbox, render::RenderBackend,
|
|
||||||
};
|
|
||||||
use crate::context::{ActionQueue, ActionType, RenderContext, UpdateContext};
|
use crate::context::{ActionQueue, ActionType, RenderContext, UpdateContext};
|
||||||
use crate::display_object::{EditText, MorphShape, MovieClip};
|
use crate::display_object::{EditText, MorphShape, MovieClip};
|
||||||
use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult, KeyCode, PlayerEvent};
|
use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult, KeyCode, PlayerEvent};
|
||||||
|
@ -183,29 +182,21 @@ pub struct Player {
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
mut renderer: Renderer,
|
renderer: Renderer,
|
||||||
audio: Audio,
|
audio: Audio,
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
input: Input,
|
input: Input,
|
||||||
movie: SwfMovie,
|
|
||||||
storage: Storage,
|
storage: Storage,
|
||||||
) -> Result<Arc<Mutex<Self>>, Error> {
|
) -> Result<Arc<Mutex<Self>>, Error> {
|
||||||
let movie = Arc::new(movie);
|
let fake_movie = Arc::new(SwfMovie::empty(NEWEST_PLAYER_VERSION));
|
||||||
|
let movie_width = 550;
|
||||||
info!(
|
let movie_height = 400;
|
||||||
"Loaded SWF version {}, with a resolution of {}x{}",
|
let frame_rate = 12.0;
|
||||||
movie.header().version,
|
|
||||||
movie.header().stage_size.x_max,
|
|
||||||
movie.header().stage_size.y_max
|
|
||||||
);
|
|
||||||
|
|
||||||
let movie_width = movie.width();
|
|
||||||
let movie_height = movie.height();
|
|
||||||
|
|
||||||
let mut player = Player {
|
let mut player = Player {
|
||||||
player_version: NEWEST_PLAYER_VERSION,
|
player_version: NEWEST_PLAYER_VERSION,
|
||||||
|
|
||||||
swf: movie.clone(),
|
swf: fake_movie,
|
||||||
|
|
||||||
is_playing: false,
|
is_playing: false,
|
||||||
needs_render: true,
|
needs_render: true,
|
||||||
|
@ -223,26 +214,10 @@ impl Player {
|
||||||
rng: SmallRng::from_seed([0u8; 16]), // TODO(Herschel): Get a proper seed on all platforms.
|
rng: SmallRng::from_seed([0u8; 16]), // TODO(Herschel): Get a proper seed on all platforms.
|
||||||
|
|
||||||
gc_arena: GcArena::new(ArenaParameters::default(), |gc_context| {
|
gc_arena: GcArena::new(ArenaParameters::default(), |gc_context| {
|
||||||
// Load and parse the device font.
|
|
||||||
let device_font =
|
|
||||||
match Self::load_device_font(gc_context, DEVICE_FONT_TAG, &mut renderer) {
|
|
||||||
Ok(font) => Some(font),
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Unable to load device font: {}", e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut library = Library::default();
|
|
||||||
|
|
||||||
library
|
|
||||||
.library_for_movie_mut(movie.clone())
|
|
||||||
.set_device_font(device_font);
|
|
||||||
|
|
||||||
GcRoot(GcCell::allocate(
|
GcRoot(GcCell::allocate(
|
||||||
gc_context,
|
gc_context,
|
||||||
GcRootData {
|
GcRootData {
|
||||||
library,
|
library: Library::default(),
|
||||||
levels: BTreeMap::new(),
|
levels: BTreeMap::new(),
|
||||||
mouse_hovered_object: None,
|
mouse_hovered_object: None,
|
||||||
drag_object: None,
|
drag_object: None,
|
||||||
|
@ -257,7 +232,7 @@ impl Player {
|
||||||
))
|
))
|
||||||
}),
|
}),
|
||||||
|
|
||||||
frame_rate: movie.header().frame_rate.into(),
|
frame_rate,
|
||||||
frame_accumulator: 0.0,
|
frame_accumulator: 0.0,
|
||||||
|
|
||||||
movie_width,
|
movie_width,
|
||||||
|
@ -281,14 +256,77 @@ impl Player {
|
||||||
storage,
|
storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
player.mutate_with_update_context(|avm1, _avm2, context| {
|
player.build_matrices();
|
||||||
|
player.audio.set_frame_rate(frame_rate);
|
||||||
|
|
||||||
|
let player_box = Arc::new(Mutex::new(player));
|
||||||
|
let mut player_lock = player_box.lock().unwrap();
|
||||||
|
player_lock.self_reference = Some(Arc::downgrade(&player_box));
|
||||||
|
std::mem::drop(player_lock);
|
||||||
|
|
||||||
|
Ok(player_box)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch the root movie.
|
||||||
|
///
|
||||||
|
/// This should not be called if a root movie fetch has already been kicked
|
||||||
|
/// off.
|
||||||
|
pub fn fetch_root_movie(&mut self, movie_url: &str) {
|
||||||
|
self.mutate_with_update_context(|_avm1, _avm2, context| {
|
||||||
|
let fetch = context.navigator.fetch(movie_url, RequestOptions::get());
|
||||||
|
let process = context.load_manager.load_root_movie(
|
||||||
|
context.player.clone().unwrap(),
|
||||||
|
fetch,
|
||||||
|
movie_url.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
context.navigator.spawn_future(process);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the root movie.
|
||||||
|
///
|
||||||
|
/// This should only be called once, as it makes no attempt at removing
|
||||||
|
/// previous stage contents. If you need to load a new root movie, you
|
||||||
|
/// should destroy and recreate the player instance.
|
||||||
|
pub fn set_root_movie(&mut self, movie: Arc<SwfMovie>) {
|
||||||
|
info!(
|
||||||
|
"Loaded SWF version {}, with a resolution of {}x{}",
|
||||||
|
movie.header().version,
|
||||||
|
movie.header().stage_size.x_max,
|
||||||
|
movie.header().stage_size.y_max
|
||||||
|
);
|
||||||
|
|
||||||
|
self.movie_width = movie.width();
|
||||||
|
self.movie_height = movie.height();
|
||||||
|
self.frame_rate = movie.header().frame_rate.into();
|
||||||
|
self.swf = movie;
|
||||||
|
|
||||||
|
self.mutate_with_update_context(|avm1, _avm2, context| {
|
||||||
let mut root: DisplayObject =
|
let mut root: DisplayObject =
|
||||||
MovieClip::from_movie(context.gc_context, movie.clone()).into();
|
MovieClip::from_movie(context.gc_context, context.swf.clone()).into();
|
||||||
root.set_depth(context.gc_context, 0);
|
root.set_depth(context.gc_context, 0);
|
||||||
root.post_instantiation(avm1, context, root, None, false);
|
root.post_instantiation(avm1, context, root, None, false);
|
||||||
root.set_name(context.gc_context, "");
|
root.set_name(context.gc_context, "");
|
||||||
context.levels.insert(0, root);
|
context.levels.insert(0, root);
|
||||||
|
|
||||||
|
// Load and parse the device font.
|
||||||
|
let device_font =
|
||||||
|
match Self::load_device_font(context.gc_context, DEVICE_FONT_TAG, context.renderer)
|
||||||
|
{
|
||||||
|
Ok(font) => Some(font),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Unable to load device font: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
context
|
||||||
|
.library
|
||||||
|
.library_for_movie_mut(context.swf.clone())
|
||||||
|
.set_device_font(device_font);
|
||||||
|
|
||||||
|
// Set the version parameter on the root.
|
||||||
let mut activation = Activation::from_nothing(
|
let mut activation = Activation::from_nothing(
|
||||||
avm1,
|
avm1,
|
||||||
ActivationIdentifier::root("[Version Setter]"),
|
ActivationIdentifier::root("[Version Setter]"),
|
||||||
|
@ -310,15 +348,9 @@ impl Player {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
player.build_matrices();
|
self.build_matrices();
|
||||||
player.preload();
|
self.preload();
|
||||||
|
self.audio.set_frame_rate(self.frame_rate);
|
||||||
let player_box = Arc::new(Mutex::new(player));
|
|
||||||
let mut player_lock = player_box.lock().unwrap();
|
|
||||||
player_lock.self_reference = Some(Arc::downgrade(&player_box));
|
|
||||||
std::mem::drop(player_lock);
|
|
||||||
|
|
||||||
Ok(player_box)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self, dt: f64) {
|
pub fn tick(&mut self, dt: f64) {
|
||||||
|
@ -1066,14 +1098,11 @@ impl Player {
|
||||||
pub fn load_device_font<'gc>(
|
pub fn load_device_font<'gc>(
|
||||||
gc_context: gc_arena::MutationContext<'gc, '_>,
|
gc_context: gc_arena::MutationContext<'gc, '_>,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
renderer: &mut Renderer,
|
renderer: &mut dyn RenderBackend,
|
||||||
) -> Result<crate::font::Font<'gc>, Error> {
|
) -> Result<crate::font::Font<'gc>, Error> {
|
||||||
let mut reader = swf::read::Reader::new(data, 8);
|
let mut reader = swf::read::Reader::new(data, 8);
|
||||||
let device_font = crate::font::Font::from_swf_tag(
|
let device_font =
|
||||||
gc_context,
|
crate::font::Font::from_swf_tag(gc_context, renderer, &reader.read_define_font_2(3)?)?;
|
||||||
renderer.deref_mut(),
|
|
||||||
&reader.read_define_font_2(3)?,
|
|
||||||
)?;
|
|
||||||
Ok(device_font)
|
Ok(device_font)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::backend::navigator::url_from_relative_path;
|
||||||
use gc_arena::Collect;
|
use gc_arena::Collect;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -17,6 +18,9 @@ pub struct SwfMovie {
|
||||||
|
|
||||||
/// Uncompressed SWF data.
|
/// Uncompressed SWF data.
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
|
|
||||||
|
/// The URL the SWF was downloaded from.
|
||||||
|
url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SwfMovie {
|
impl SwfMovie {
|
||||||
|
@ -31,25 +35,37 @@ impl SwfMovie {
|
||||||
num_frames: 0,
|
num_frames: 0,
|
||||||
},
|
},
|
||||||
data: vec![],
|
data: vec![],
|
||||||
|
url: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a movie from an existing movie with any particular data on it.
|
/// Construct a movie from an existing movie with any particular data on
|
||||||
pub fn from_movie_and_subdata(&self, data: Vec<u8>) -> Self {
|
/// it.
|
||||||
|
///
|
||||||
|
/// Use of this method is discouraged. SWF data should be borrowed or
|
||||||
|
/// sliced as necessary to refer to partial sections of a file.
|
||||||
|
pub fn from_movie_and_subdata(&self, data: Vec<u8>, source: &SwfMovie) -> Self {
|
||||||
Self {
|
Self {
|
||||||
header: self.header.clone(),
|
header: self.header.clone(),
|
||||||
data,
|
data,
|
||||||
|
url: source.url.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Utility method to construct a movie from a file on disk.
|
/// Utility method to construct a movie from a file on disk.
|
||||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||||
|
let mut url = path.as_ref().to_string_lossy().to_owned().to_string();
|
||||||
|
let cwd = std::env::current_dir()?;
|
||||||
|
if let Ok(abs_url) = url_from_relative_path(cwd, &url) {
|
||||||
|
url = abs_url.into_string();
|
||||||
|
}
|
||||||
|
|
||||||
let data = std::fs::read(path)?;
|
let data = std::fs::read(path)?;
|
||||||
Self::from_data(&data)
|
Self::from_data(&data, Some(url))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a movie based on the contents of the SWF datastream.
|
/// Construct a movie based on the contents of the SWF datastream.
|
||||||
pub fn from_data(swf_data: &[u8]) -> Result<Self, Error> {
|
pub fn from_data(swf_data: &[u8], url: Option<String>) -> Result<Self, Error> {
|
||||||
let swf_stream = swf::read::read_swf_header(&swf_data[..])?;
|
let swf_stream = swf::read::read_swf_header(&swf_data[..])?;
|
||||||
let header = swf_stream.header;
|
let header = swf_stream.header;
|
||||||
let mut reader = swf_stream.reader;
|
let mut reader = swf_stream.reader;
|
||||||
|
@ -74,7 +90,7 @@ impl SwfMovie {
|
||||||
data
|
data
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self { header, data })
|
Ok(Self { header, data, url })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn header(&self) -> &Header {
|
pub fn header(&self) -> &Header {
|
||||||
|
@ -97,6 +113,11 @@ impl SwfMovie {
|
||||||
pub fn height(&self) -> u32 {
|
pub fn height(&self) -> u32 {
|
||||||
(self.header.stage_size.y_max - self.header.stage_size.y_min).to_pixels() as u32
|
(self.header.stage_size.y_max - self.header.stage_size.y_min).to_pixels() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the URL this SWF was fetched from.
|
||||||
|
pub fn url(&self) -> Option<&str> {
|
||||||
|
self.url.as_deref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A shared-ownership reference to some portion of an SWF datastream.
|
/// A shared-ownership reference to some portion of an SWF datastream.
|
||||||
|
@ -141,12 +162,12 @@ impl SwfSlice {
|
||||||
/// Construct a new slice with a given dataset only.
|
/// Construct a new slice with a given dataset only.
|
||||||
///
|
///
|
||||||
/// This is used primarily for converting owned data back into a slice: we
|
/// This is used primarily for converting owned data back into a slice: we
|
||||||
/// reattach the SWF data that we can
|
/// reattach the SWF data to a fresh movie and return a new slice into it.
|
||||||
pub fn owned_subslice(&self, data: Vec<u8>) -> Self {
|
pub fn owned_subslice(&self, data: Vec<u8>, source: &SwfMovie) -> Self {
|
||||||
let len = data.len();
|
let len = data.len();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
movie: Arc::new(self.movie.from_movie_and_subdata(data)),
|
movie: Arc::new(self.movie.from_movie_and_subdata(data, source)),
|
||||||
start: 0,
|
start: 0,
|
||||||
end: len,
|
end: len,
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ use ruffle_core::tag_utils::SwfMovie;
|
||||||
use ruffle_core::Player;
|
use ruffle_core::Player;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
|
@ -366,9 +367,9 @@ fn run_swf(swf_path: &str, num_frames: u32) -> Result<String, Error> {
|
||||||
Box::new(NullAudioBackend::new()),
|
Box::new(NullAudioBackend::new()),
|
||||||
Box::new(NullNavigatorBackend::with_base_path(base_path, channel)),
|
Box::new(NullNavigatorBackend::with_base_path(base_path, channel)),
|
||||||
Box::new(NullInputBackend::new()),
|
Box::new(NullInputBackend::new()),
|
||||||
movie,
|
|
||||||
Box::new(MemoryStorageBackend::default()),
|
Box::new(MemoryStorageBackend::default()),
|
||||||
)?;
|
)?;
|
||||||
|
player.lock().unwrap().set_root_movie(Arc::new(movie));
|
||||||
|
|
||||||
for _ in 0..num_frames {
|
for _ in 0..num_frames {
|
||||||
player.lock().unwrap().run_frame();
|
player.lock().unwrap().run_frame();
|
||||||
|
|
|
@ -16,6 +16,7 @@ use ruffle_core::{
|
||||||
};
|
};
|
||||||
use ruffle_render_wgpu::WgpuRenderBackend;
|
use ruffle_render_wgpu::WgpuRenderBackend;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
@ -92,7 +93,8 @@ fn run_player(input_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let storage = Box::new(DiskStorageBackend::new(
|
let storage = Box::new(DiskStorageBackend::new(
|
||||||
input_path.file_name().unwrap_or_default().as_ref(),
|
input_path.file_name().unwrap_or_default().as_ref(),
|
||||||
));
|
));
|
||||||
let player = Player::new(renderer, audio, navigator, input, movie, storage)?;
|
let player = Player::new(renderer, audio, navigator, input, storage)?;
|
||||||
|
player.lock().unwrap().set_root_movie(Arc::new(movie));
|
||||||
player.lock().unwrap().set_is_playing(true); // Desktop player will auto-play.
|
player.lock().unwrap().set_is_playing(true); // Desktop player will auto-play.
|
||||||
|
|
||||||
player
|
player
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
use crate::custom_event::RuffleEvent;
|
use crate::custom_event::RuffleEvent;
|
||||||
use ruffle_core::backend::navigator::{
|
use ruffle_core::backend::navigator::{
|
||||||
NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
|
url_from_relative_path, NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
|
||||||
};
|
};
|
||||||
use ruffle_core::loader::Error;
|
use ruffle_core::loader::Error;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -129,4 +130,13 @@ impl NavigatorBackend for ExternalNavigatorBackend {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str> {
|
||||||
|
let relative = url_from_relative_path(&self.relative_base_path, url);
|
||||||
|
if let Ok(relative) = relative {
|
||||||
|
relative.into_string().into()
|
||||||
|
} else {
|
||||||
|
url.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ use std::error::Error;
|
||||||
use std::fs::create_dir_all;
|
use std::fs::create_dir_all;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::Arc;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use walkdir::{DirEntry, WalkDir};
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
|
@ -84,7 +85,6 @@ fn take_screenshot(
|
||||||
Box::new(NullAudioBackend::new()),
|
Box::new(NullAudioBackend::new()),
|
||||||
Box::new(NullNavigatorBackend::new()),
|
Box::new(NullNavigatorBackend::new()),
|
||||||
Box::new(NullInputBackend::new()),
|
Box::new(NullInputBackend::new()),
|
||||||
movie,
|
|
||||||
Box::new(MemoryStorageBackend::default()),
|
Box::new(MemoryStorageBackend::default()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ fn take_screenshot(
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_viewport_dimensions(width, height);
|
.set_viewport_dimensions(width, height);
|
||||||
|
player.lock().unwrap().set_root_movie(Arc::new(movie));
|
||||||
|
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
let totalframes = frames + skipframes;
|
let totalframes = frames + skipframes;
|
||||||
|
|
|
@ -116,28 +116,48 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure a fresh Ruffle instance is ready on this player before continuing.
|
||||||
|
*
|
||||||
|
* @throws Any exceptions generated by loading Ruffle Core will be logged
|
||||||
|
* and passed on.
|
||||||
|
*/
|
||||||
|
async ensure_fresh_instance() {
|
||||||
|
if (this.instance) {
|
||||||
|
this.instance.destroy();
|
||||||
|
this.instance = null;
|
||||||
|
console.log("Ruffle instance destroyed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ruffle = await this.Ruffle.catch(function (e) {
|
||||||
|
console.error("Serious error loading Ruffle: " + e);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.instance = Ruffle.new(this.container);
|
||||||
|
console.log("New Ruffle instance created.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a movie into this Ruffle Player instance by URL.
|
||||||
|
*
|
||||||
|
* Any existing movie will be immediately stopped, while the new movie's
|
||||||
|
* load happens asynchronously. There is currently no way to await the file
|
||||||
|
* being loaded, or any errors that happen loading it.
|
||||||
|
*
|
||||||
|
* @param {String} url The URL to stream.
|
||||||
|
*/
|
||||||
async stream_swf_url(url) {
|
async stream_swf_url(url) {
|
||||||
//TODO: Actually stream files...
|
//TODO: Actually stream files...
|
||||||
try {
|
try {
|
||||||
if (this.isConnected && !this.is_unused_fallback_object()) {
|
if (this.isConnected && !this.is_unused_fallback_object()) {
|
||||||
let abs_url = new URL(url, window.location.href).toString();
|
|
||||||
console.log("Loading SWF file " + url);
|
console.log("Loading SWF file " + url);
|
||||||
|
|
||||||
let response = await fetch(abs_url);
|
await this.ensure_fresh_instance();
|
||||||
|
this.instance.stream_from(url);
|
||||||
|
|
||||||
if (response.ok) {
|
if (this.play_button) {
|
||||||
let data = await response.arrayBuffer();
|
this.play_button.style.display = "block";
|
||||||
await this.play_swf_data(data);
|
|
||||||
console.log("Playing " + url);
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
"SWF load failed: " +
|
|
||||||
response.status +
|
|
||||||
" " +
|
|
||||||
response.statusText +
|
|
||||||
" for " +
|
|
||||||
url
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
@ -159,22 +179,23 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a movie's data into this Ruffle Player instance.
|
||||||
|
*
|
||||||
|
* Any existing movie will be immediately stopped, and the new movie's data
|
||||||
|
* placed into a fresh Stage on the same stack.
|
||||||
|
*
|
||||||
|
* Please note that by doing this, no URL information will be provided to
|
||||||
|
* the movie being loaded.
|
||||||
|
*
|
||||||
|
* @param {String} url The URL to stream.
|
||||||
|
*/
|
||||||
async play_swf_data(data) {
|
async play_swf_data(data) {
|
||||||
if (this.isConnected && !this.is_unused_fallback_object()) {
|
if (this.isConnected && !this.is_unused_fallback_object()) {
|
||||||
console.log("Got SWF data");
|
console.log("Got SWF data");
|
||||||
|
|
||||||
if (this.instance) {
|
await this.ensure_fresh_instance();
|
||||||
this.instance.destroy();
|
this.instance.load_data(new Uint8Array(data));
|
||||||
this.instance = null;
|
|
||||||
console.log("Ruffle instance destroyed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let Ruffle = await this.Ruffle.catch(function (e) {
|
|
||||||
console.error("Serious error loading Ruffle: " + e);
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.instance = Ruffle.new(this.container, new Uint8Array(data));
|
|
||||||
console.log("New Ruffle instance created.");
|
console.log("New Ruffle instance created.");
|
||||||
|
|
||||||
if (this.play_button) {
|
if (this.play_button) {
|
||||||
|
|
|
@ -14,7 +14,6 @@ use ruffle_core::backend::storage::StorageBackend;
|
||||||
use ruffle_core::tag_utils::SwfMovie;
|
use ruffle_core::tag_utils::SwfMovie;
|
||||||
use ruffle_core::PlayerEvent;
|
use ruffle_core::PlayerEvent;
|
||||||
use ruffle_web_common::JsResult;
|
use ruffle_web_common::JsResult;
|
||||||
use std::mem::drop;
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::{cell::RefCell, error::Error, num::NonZeroI32};
|
use std::{cell::RefCell, error::Error, num::NonZeroI32};
|
||||||
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
||||||
|
@ -57,8 +56,38 @@ pub struct Ruffle(Index);
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl Ruffle {
|
impl Ruffle {
|
||||||
pub fn new(parent: HtmlElement, swf_data: Uint8Array) -> Result<Ruffle, JsValue> {
|
pub fn new(parent: HtmlElement) -> Result<Ruffle, JsValue> {
|
||||||
Ruffle::new_internal(parent, swf_data).map_err(|_| "Error creating player".into())
|
Ruffle::new_internal(parent).map_err(|_| "Error creating player".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stream an arbitrary movie file from (presumably) the Internet.
|
||||||
|
///
|
||||||
|
/// This method should only be called once per player.
|
||||||
|
pub fn stream_from(&mut self, movie_url: &str) {
|
||||||
|
INSTANCES.with(|instances| {
|
||||||
|
let mut instances = instances.borrow_mut();
|
||||||
|
let instance = instances.get_mut(self.0).unwrap();
|
||||||
|
instance.core.lock().unwrap().fetch_root_movie(movie_url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Play an arbitrary movie on this instance.
|
||||||
|
///
|
||||||
|
/// This method should only be called once per player.
|
||||||
|
pub fn load_data(&mut self, swf_data: Uint8Array) -> Result<(), JsValue> {
|
||||||
|
let movie = Arc::new({
|
||||||
|
let mut data = vec![0; swf_data.length() as usize];
|
||||||
|
swf_data.copy_to(&mut data[..]);
|
||||||
|
SwfMovie::from_data(&data, None).map_err(|e| format!("Error loading movie: {}", e))?
|
||||||
|
});
|
||||||
|
|
||||||
|
INSTANCES.with(|instances| {
|
||||||
|
let mut instances = instances.borrow_mut();
|
||||||
|
let instance = instances.get_mut(self.0).unwrap();
|
||||||
|
instance.core.lock().unwrap().set_root_movie(movie);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play(&mut self) {
|
pub fn play(&mut self) {
|
||||||
|
@ -106,16 +135,10 @@ impl Ruffle {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ruffle {
|
impl Ruffle {
|
||||||
fn new_internal(parent: HtmlElement, swf_data: Uint8Array) -> Result<Ruffle, Box<dyn Error>> {
|
fn new_internal(parent: HtmlElement) -> Result<Ruffle, Box<dyn Error>> {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
let _ = console_log::init_with_level(log::Level::Trace);
|
let _ = console_log::init_with_level(log::Level::Trace);
|
||||||
|
|
||||||
let movie = {
|
|
||||||
let mut data = vec![0; swf_data.length() as usize];
|
|
||||||
swf_data.copy_to(&mut data[..]);
|
|
||||||
SwfMovie::from_data(&data)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let window = web_sys::window().ok_or_else(|| "Expected window")?;
|
let window = web_sys::window().ok_or_else(|| "Expected window")?;
|
||||||
let document = window.document().ok_or("Expected document")?;
|
let document = window.document().ok_or("Expected document")?;
|
||||||
|
|
||||||
|
@ -138,12 +161,7 @@ impl Ruffle {
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| Box::new(MemoryStorageBackend::default()));
|
.unwrap_or_else(|| Box::new(MemoryStorageBackend::default()));
|
||||||
|
|
||||||
let core =
|
let core = ruffle_core::Player::new(renderer, audio, navigator, input, local_storage)?;
|
||||||
ruffle_core::Player::new(renderer, audio, navigator, input, movie, local_storage)?;
|
|
||||||
let mut core_lock = core.lock().unwrap();
|
|
||||||
let frame_rate = core_lock.frame_rate();
|
|
||||||
core_lock.audio_mut().set_frame_rate(frame_rate);
|
|
||||||
drop(core_lock);
|
|
||||||
|
|
||||||
// Create instance.
|
// Create instance.
|
||||||
let instance = RuffleInstance {
|
let instance = RuffleInstance {
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
use js_sys::{Array, ArrayBuffer, Uint8Array};
|
use js_sys::{Array, ArrayBuffer, Uint8Array};
|
||||||
use ruffle_core::backend::navigator::{
|
use ruffle_core::backend::navigator::{
|
||||||
NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
|
url_from_relative_url, NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
|
||||||
};
|
};
|
||||||
use ruffle_core::loader::Error;
|
use ruffle_core::loader::Error;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
|
@ -158,4 +159,17 @@ impl NavigatorBackend for WebNavigatorBackend {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_relative_url<'a>(&mut self, url: &'a str) -> Cow<'a, str> {
|
||||||
|
let window = web_sys::window().expect("window()");
|
||||||
|
let document = window.document().expect("document()");
|
||||||
|
|
||||||
|
if let Ok(Some(base_uri)) = document.base_uri() {
|
||||||
|
if let Ok(new_url) = url_from_relative_url(&base_uri, url) {
|
||||||
|
return new_url.into_string().into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
url.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue