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(),
|
||||
level,
|
||||
fetch,
|
||||
url,
|
||||
None,
|
||||
);
|
||||
context.navigator.spawn_future(process);
|
||||
|
@ -1336,6 +1337,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
|
|||
context.player.clone().unwrap(),
|
||||
clip_target,
|
||||
fetch,
|
||||
url.to_string(),
|
||||
None,
|
||||
);
|
||||
context.navigator.spawn_future(process);
|
||||
|
@ -1353,6 +1355,7 @@ impl<'a, 'gc: 'a> Activation<'a, 'gc> {
|
|||
context.player.clone().unwrap(),
|
||||
level,
|
||||
fetch,
|
||||
url.to_string(),
|
||||
None,
|
||||
);
|
||||
context.navigator.spawn_future(process);
|
||||
|
|
|
@ -1096,6 +1096,7 @@ fn load_movie<'gc>(
|
|||
context.player.clone().unwrap(),
|
||||
DisplayObject::MovieClip(target),
|
||||
fetch,
|
||||
url.to_string(),
|
||||
None,
|
||||
);
|
||||
|
||||
|
|
|
@ -135,6 +135,7 @@ pub fn load_clip<'gc>(
|
|||
context.player.clone().unwrap(),
|
||||
DisplayObject::MovieClip(movieclip),
|
||||
fetch,
|
||||
url.to_string(),
|
||||
Some(this),
|
||||
);
|
||||
|
||||
|
|
|
@ -898,11 +898,15 @@ fn drop_target<'gc>(
|
|||
|
||||
fn url<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc>,
|
||||
_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
_this: DisplayObject<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: DisplayObject<'gc>,
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
log::warn!("Unimplemented property _url");
|
||||
Ok("".into())
|
||||
Ok(this
|
||||
.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>(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Browser-related platform functions
|
||||
|
||||
use crate::loader::Error;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fs;
|
||||
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::time::Duration;
|
||||
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.
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -132,6 +184,16 @@ pub trait NavigatorBackend {
|
|||
/// TODO: For some reason, `wasm_bindgen_futures` wants unpinnable futures.
|
||||
/// This seems highly limiting.
|
||||
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.
|
||||
|
@ -303,4 +365,13 @@ impl NavigatorBackend for NullNavigatorBackend {
|
|||
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 {
|
||||
let mut actions = vec![];
|
||||
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 {
|
||||
let button_action = ButtonAction {
|
||||
action_data: action_data.clone(),
|
||||
|
|
|
@ -2567,7 +2567,7 @@ impl ClipAction {
|
|||
|
||||
let len = other.action_data.len();
|
||||
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 {
|
||||
event: match event {
|
||||
ClipEventFlag::Construct => ClipEvent::Construct,
|
||||
|
|
|
@ -424,6 +424,7 @@ mod tests {
|
|||
use crate::font::{EvalParameters, Font};
|
||||
use crate::player::{Player, DEVICE_FONT_TAG};
|
||||
use gc_arena::{rootless_arena, MutationContext};
|
||||
use std::ops::DerefMut;
|
||||
use swf::Twips;
|
||||
|
||||
fn with_device_font<F>(callback: F)
|
||||
|
@ -432,7 +433,8 @@ mod tests {
|
|||
{
|
||||
rootless_arena(|mc| {
|
||||
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);
|
||||
})
|
||||
|
|
|
@ -22,6 +22,9 @@ pub enum Error {
|
|||
#[error("Load cancelled")]
|
||||
Cancelled,
|
||||
|
||||
#[error("Non-root-movie loader spawned as root movie loader")]
|
||||
NotRootMovieLoader,
|
||||
|
||||
#[error("Non-movie loader spawned as movie loader")]
|
||||
NotMovieLoader,
|
||||
|
||||
|
@ -34,6 +37,9 @@ pub enum Error {
|
|||
#[error("Non-XML loader spawned as XML loader")]
|
||||
NotXmlLoader,
|
||||
|
||||
#[error("Could not fetch movie {0}")]
|
||||
FetchError(String),
|
||||
|
||||
#[error("Invalid SWF")]
|
||||
InvalidSwf(#[from] crate::tag_utils::Error),
|
||||
|
||||
|
@ -108,6 +114,27 @@ impl<'gc> LoadManager<'gc> {
|
|||
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.
|
||||
///
|
||||
/// Returns the loader's async process, which you will need to spawn.
|
||||
|
@ -116,6 +143,7 @@ impl<'gc> LoadManager<'gc> {
|
|||
player: Weak<Mutex<Player>>,
|
||||
target_clip: DisplayObject<'gc>,
|
||||
fetch: OwnedFuture<Vec<u8>, Error>,
|
||||
url: String,
|
||||
target_broadcaster: Option<Object<'gc>>,
|
||||
) -> OwnedFuture<(), Error> {
|
||||
let loader = Loader::Movie {
|
||||
|
@ -129,7 +157,7 @@ impl<'gc> LoadManager<'gc> {
|
|||
let loader = self.get_loader_mut(handle).unwrap();
|
||||
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).
|
||||
|
@ -229,6 +257,12 @@ impl<'gc> Default for LoadManager<'gc> {
|
|||
|
||||
/// A struct that holds garbage-collected pointers for asynchronous code.
|
||||
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.
|
||||
Movie {
|
||||
/// The handle to refer to this loader instance.
|
||||
|
@ -291,6 +325,7 @@ pub enum Loader<'gc> {
|
|||
unsafe impl<'gc> Collect for Loader<'gc> {
|
||||
fn trace(&self, cc: CollectionContext) {
|
||||
match self {
|
||||
Loader::RootMovie { .. } => {}
|
||||
Loader::Movie {
|
||||
target_clip,
|
||||
target_broadcaster,
|
||||
|
@ -313,6 +348,7 @@ impl<'gc> Loader<'gc> {
|
|||
/// run.
|
||||
pub fn introduce_loader_handle(&mut self, handle: Handle) {
|
||||
match self {
|
||||
Loader::RootMovie { self_handle, .. } => *self_handle = Some(handle),
|
||||
Loader::Movie { self_handle, .. } => *self_handle = Some(handle),
|
||||
Loader::Form { 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.
|
||||
///
|
||||
/// The given future should be passed immediately to an executor; it will
|
||||
|
@ -331,6 +407,7 @@ impl<'gc> Loader<'gc> {
|
|||
&mut self,
|
||||
player: Weak<Mutex<Player>>,
|
||||
fetch: OwnedFuture<Vec<u8>, Error>,
|
||||
mut url: String,
|
||||
) -> OwnedFuture<(), Error> {
|
||||
let handle = match self {
|
||||
Loader::Movie { self_handle, .. } => self_handle.expect("Loader not self-introduced"),
|
||||
|
@ -344,6 +421,8 @@ impl<'gc> Loader<'gc> {
|
|||
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();
|
||||
|
||||
let (clip, broadcaster) = match uc.load_manager.get_loader(handle) {
|
||||
Some(Loader::Movie {
|
||||
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 {
|
||||
let movie = Arc::new(movie);
|
||||
|
||||
|
|
|
@ -6,10 +6,9 @@ use crate::avm1::object::Object;
|
|||
use crate::avm1::{Avm1, AvmString, TObject, Timers, Value};
|
||||
use crate::avm2::Avm2;
|
||||
use crate::backend::input::{InputBackend, MouseCursor};
|
||||
use crate::backend::navigator::{NavigatorBackend, RequestOptions};
|
||||
use crate::backend::storage::StorageBackend;
|
||||
use crate::backend::{
|
||||
audio::AudioBackend, navigator::NavigatorBackend, render::Letterbox, render::RenderBackend,
|
||||
};
|
||||
use crate::backend::{audio::AudioBackend, render::Letterbox, render::RenderBackend};
|
||||
use crate::context::{ActionQueue, ActionType, RenderContext, UpdateContext};
|
||||
use crate::display_object::{EditText, MorphShape, MovieClip};
|
||||
use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult, KeyCode, PlayerEvent};
|
||||
|
@ -183,29 +182,21 @@ pub struct Player {
|
|||
|
||||
impl Player {
|
||||
pub fn new(
|
||||
mut renderer: Renderer,
|
||||
renderer: Renderer,
|
||||
audio: Audio,
|
||||
navigator: Navigator,
|
||||
input: Input,
|
||||
movie: SwfMovie,
|
||||
storage: Storage,
|
||||
) -> Result<Arc<Mutex<Self>>, Error> {
|
||||
let movie = Arc::new(movie);
|
||||
|
||||
info!(
|
||||
"Loaded SWF version {}, with a resolution of {}x{}",
|
||||
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 fake_movie = Arc::new(SwfMovie::empty(NEWEST_PLAYER_VERSION));
|
||||
let movie_width = 550;
|
||||
let movie_height = 400;
|
||||
let frame_rate = 12.0;
|
||||
|
||||
let mut player = Player {
|
||||
player_version: NEWEST_PLAYER_VERSION,
|
||||
|
||||
swf: movie.clone(),
|
||||
swf: fake_movie,
|
||||
|
||||
is_playing: false,
|
||||
needs_render: true,
|
||||
|
@ -223,26 +214,10 @@ impl Player {
|
|||
rng: SmallRng::from_seed([0u8; 16]), // TODO(Herschel): Get a proper seed on all platforms.
|
||||
|
||||
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(
|
||||
gc_context,
|
||||
GcRootData {
|
||||
library,
|
||||
library: Library::default(),
|
||||
levels: BTreeMap::new(),
|
||||
mouse_hovered_object: None,
|
||||
drag_object: None,
|
||||
|
@ -257,7 +232,7 @@ impl Player {
|
|||
))
|
||||
}),
|
||||
|
||||
frame_rate: movie.header().frame_rate.into(),
|
||||
frame_rate,
|
||||
frame_accumulator: 0.0,
|
||||
|
||||
movie_width,
|
||||
|
@ -281,14 +256,77 @@ impl Player {
|
|||
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 =
|
||||
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.post_instantiation(avm1, context, root, None, false);
|
||||
root.set_name(context.gc_context, "");
|
||||
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(
|
||||
avm1,
|
||||
ActivationIdentifier::root("[Version Setter]"),
|
||||
|
@ -310,15 +348,9 @@ impl Player {
|
|||
);
|
||||
});
|
||||
|
||||
player.build_matrices();
|
||||
player.preload();
|
||||
|
||||
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)
|
||||
self.build_matrices();
|
||||
self.preload();
|
||||
self.audio.set_frame_rate(self.frame_rate);
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, dt: f64) {
|
||||
|
@ -1066,14 +1098,11 @@ impl Player {
|
|||
pub fn load_device_font<'gc>(
|
||||
gc_context: gc_arena::MutationContext<'gc, '_>,
|
||||
data: &[u8],
|
||||
renderer: &mut Renderer,
|
||||
renderer: &mut dyn RenderBackend,
|
||||
) -> Result<crate::font::Font<'gc>, Error> {
|
||||
let mut reader = swf::read::Reader::new(data, 8);
|
||||
let device_font = crate::font::Font::from_swf_tag(
|
||||
gc_context,
|
||||
renderer.deref_mut(),
|
||||
&reader.read_define_font_2(3)?,
|
||||
)?;
|
||||
let device_font =
|
||||
crate::font::Font::from_swf_tag(gc_context, renderer, &reader.read_define_font_2(3)?)?;
|
||||
Ok(device_font)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::backend::navigator::url_from_relative_path;
|
||||
use gc_arena::Collect;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
@ -17,6 +18,9 @@ pub struct SwfMovie {
|
|||
|
||||
/// Uncompressed SWF data.
|
||||
data: Vec<u8>,
|
||||
|
||||
/// The URL the SWF was downloaded from.
|
||||
url: Option<String>,
|
||||
}
|
||||
|
||||
impl SwfMovie {
|
||||
|
@ -31,25 +35,37 @@ impl SwfMovie {
|
|||
num_frames: 0,
|
||||
},
|
||||
data: vec![],
|
||||
url: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a movie from an existing movie with any particular data on it.
|
||||
pub fn from_movie_and_subdata(&self, data: Vec<u8>) -> Self {
|
||||
/// Construct a movie from an existing movie with any particular data on
|
||||
/// 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 {
|
||||
header: self.header.clone(),
|
||||
data,
|
||||
url: source.url.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility method to construct a movie from a file on disk.
|
||||
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)?;
|
||||
Self::from_data(&data)
|
||||
Self::from_data(&data, Some(url))
|
||||
}
|
||||
|
||||
/// 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 header = swf_stream.header;
|
||||
let mut reader = swf_stream.reader;
|
||||
|
@ -74,7 +90,7 @@ impl SwfMovie {
|
|||
data
|
||||
};
|
||||
|
||||
Ok(Self { header, data })
|
||||
Ok(Self { header, data, url })
|
||||
}
|
||||
|
||||
pub fn header(&self) -> &Header {
|
||||
|
@ -97,6 +113,11 @@ impl SwfMovie {
|
|||
pub fn height(&self) -> 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.
|
||||
|
@ -141,12 +162,12 @@ impl SwfSlice {
|
|||
/// Construct a new slice with a given dataset only.
|
||||
///
|
||||
/// This is used primarily for converting owned data back into a slice: we
|
||||
/// reattach the SWF data that we can
|
||||
pub fn owned_subslice(&self, data: Vec<u8>) -> Self {
|
||||
/// reattach the SWF data to a fresh movie and return a new slice into it.
|
||||
pub fn owned_subslice(&self, data: Vec<u8>, source: &SwfMovie) -> Self {
|
||||
let len = data.len();
|
||||
|
||||
Self {
|
||||
movie: Arc::new(self.movie.from_movie_and_subdata(data)),
|
||||
movie: Arc::new(self.movie.from_movie_and_subdata(data, source)),
|
||||
start: 0,
|
||||
end: len,
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ use ruffle_core::tag_utils::SwfMovie;
|
|||
use ruffle_core::Player;
|
||||
use std::cell::RefCell;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
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(NullNavigatorBackend::with_base_path(base_path, channel)),
|
||||
Box::new(NullInputBackend::new()),
|
||||
movie,
|
||||
Box::new(MemoryStorageBackend::default()),
|
||||
)?;
|
||||
player.lock().unwrap().set_root_movie(Arc::new(movie));
|
||||
|
||||
for _ in 0..num_frames {
|
||||
player.lock().unwrap().run_frame();
|
||||
|
|
|
@ -16,6 +16,7 @@ use ruffle_core::{
|
|||
};
|
||||
use ruffle_render_wgpu::WgpuRenderBackend;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
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(
|
||||
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
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
use crate::custom_event::RuffleEvent;
|
||||
use ruffle_core::backend::navigator::{
|
||||
NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
|
||||
url_from_relative_path, NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
|
||||
};
|
||||
use ruffle_core::loader::Error;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
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::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use structopt::StructOpt;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
|
@ -84,7 +85,6 @@ fn take_screenshot(
|
|||
Box::new(NullAudioBackend::new()),
|
||||
Box::new(NullNavigatorBackend::new()),
|
||||
Box::new(NullInputBackend::new()),
|
||||
movie,
|
||||
Box::new(MemoryStorageBackend::default()),
|
||||
)?;
|
||||
|
||||
|
@ -92,6 +92,7 @@ fn take_screenshot(
|
|||
.lock()
|
||||
.unwrap()
|
||||
.set_viewport_dimensions(width, height);
|
||||
player.lock().unwrap().set_root_movie(Arc::new(movie));
|
||||
|
||||
let mut result = Vec::new();
|
||||
let totalframes = frames + skipframes;
|
||||
|
|
|
@ -116,28 +116,48 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement {
|
|||
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) {
|
||||
//TODO: Actually stream files...
|
||||
try {
|
||||
if (this.isConnected && !this.is_unused_fallback_object()) {
|
||||
let abs_url = new URL(url, window.location.href).toString();
|
||||
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) {
|
||||
let data = await response.arrayBuffer();
|
||||
await this.play_swf_data(data);
|
||||
console.log("Playing " + url);
|
||||
} else {
|
||||
console.error(
|
||||
"SWF load failed: " +
|
||||
response.status +
|
||||
" " +
|
||||
response.statusText +
|
||||
" for " +
|
||||
url
|
||||
);
|
||||
if (this.play_button) {
|
||||
this.play_button.style.display = "block";
|
||||
}
|
||||
} else {
|
||||
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) {
|
||||
if (this.isConnected && !this.is_unused_fallback_object()) {
|
||||
console.log("Got SWF data");
|
||||
|
||||
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, new Uint8Array(data));
|
||||
await this.ensure_fresh_instance();
|
||||
this.instance.load_data(new Uint8Array(data));
|
||||
console.log("New Ruffle instance created.");
|
||||
|
||||
if (this.play_button) {
|
||||
|
|
|
@ -14,7 +14,6 @@ use ruffle_core::backend::storage::StorageBackend;
|
|||
use ruffle_core::tag_utils::SwfMovie;
|
||||
use ruffle_core::PlayerEvent;
|
||||
use ruffle_web_common::JsResult;
|
||||
use std::mem::drop;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{cell::RefCell, error::Error, num::NonZeroI32};
|
||||
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
||||
|
@ -57,8 +56,38 @@ pub struct Ruffle(Index);
|
|||
|
||||
#[wasm_bindgen]
|
||||
impl Ruffle {
|
||||
pub fn new(parent: HtmlElement, swf_data: Uint8Array) -> Result<Ruffle, JsValue> {
|
||||
Ruffle::new_internal(parent, swf_data).map_err(|_| "Error creating player".into())
|
||||
pub fn new(parent: HtmlElement) -> Result<Ruffle, JsValue> {
|
||||
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) {
|
||||
|
@ -106,16 +135,10 @@ 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();
|
||||
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 document = window.document().ok_or("Expected document")?;
|
||||
|
||||
|
@ -138,12 +161,7 @@ impl Ruffle {
|
|||
})
|
||||
.unwrap_or_else(|| Box::new(MemoryStorageBackend::default()));
|
||||
|
||||
let core =
|
||||
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);
|
||||
let core = ruffle_core::Player::new(renderer, audio, navigator, input, local_storage)?;
|
||||
|
||||
// Create instance.
|
||||
let instance = RuffleInstance {
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
use js_sys::{Array, ArrayBuffer, Uint8Array};
|
||||
use ruffle_core::backend::navigator::{
|
||||
NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
|
||||
url_from_relative_url, NavigationMethod, NavigatorBackend, OwnedFuture, RequestOptions,
|
||||
};
|
||||
use ruffle_core::loader::Error;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
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