core: Add flashvars support - #312
This commit is contained in:
parent
a6b952e44e
commit
6d9155477c
|
@ -30,7 +30,7 @@ mod library;
|
|||
pub mod loader;
|
||||
mod player;
|
||||
mod prelude;
|
||||
mod property_map;
|
||||
pub mod property_map;
|
||||
pub mod shape_utils;
|
||||
pub mod string_utils;
|
||||
pub mod tag_utils;
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::backend::navigator::OwnedFuture;
|
|||
use crate::context::{ActionQueue, ActionType};
|
||||
use crate::display_object::{DisplayObject, MorphShape, TDisplayObject};
|
||||
use crate::player::{Player, NEWEST_PLAYER_VERSION};
|
||||
use crate::property_map::PropertyMap;
|
||||
use crate::tag_utils::SwfMovie;
|
||||
use crate::vminterface::Instantiator;
|
||||
use crate::xml::XMLNode;
|
||||
|
@ -121,6 +122,7 @@ impl<'gc> LoadManager<'gc> {
|
|||
player: Weak<Mutex<Player>>,
|
||||
fetch: OwnedFuture<Vec<u8>, Error>,
|
||||
url: String,
|
||||
parameters: PropertyMap<String>,
|
||||
) -> OwnedFuture<(), Error> {
|
||||
let loader = Loader::RootMovie { self_handle: None };
|
||||
let handle = self.add_loader(loader);
|
||||
|
@ -128,7 +130,7 @@ impl<'gc> LoadManager<'gc> {
|
|||
let loader = self.get_loader_mut(handle).unwrap();
|
||||
loader.introduce_loader_handle(handle);
|
||||
|
||||
loader.root_movie_loader(player, fetch, url)
|
||||
loader.root_movie_loader(player, fetch, url, parameters)
|
||||
}
|
||||
|
||||
/// Kick off a movie clip load.
|
||||
|
@ -358,6 +360,7 @@ impl<'gc> Loader<'gc> {
|
|||
player: Weak<Mutex<Player>>,
|
||||
fetch: OwnedFuture<Vec<u8>, Error>,
|
||||
mut url: String,
|
||||
parameters: PropertyMap<String>,
|
||||
) -> OwnedFuture<(), Error> {
|
||||
let _handle = match self {
|
||||
Loader::RootMovie { self_handle, .. } => {
|
||||
|
@ -383,7 +386,10 @@ impl<'gc> Loader<'gc> {
|
|||
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, mut movie)) = data {
|
||||
for (key, value) in parameters.iter() {
|
||||
movie.parameters_mut().insert(key, value.to_owned(), false);
|
||||
}
|
||||
player.lock().unwrap().set_root_movie(Arc::new(movie));
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::avm1::activation::{Activation, ActivationIdentifier};
|
|||
use crate::avm1::debug::VariableDumper;
|
||||
use crate::avm1::globals::system::SystemProperties;
|
||||
use crate::avm1::object::Object;
|
||||
use crate::avm1::{Avm1, AvmString, TObject, Timers, Value};
|
||||
use crate::avm1::{Avm1, AvmString, ScriptObject, TObject, Timers, Value};
|
||||
use crate::avm2::Avm2;
|
||||
use crate::backend::input::{InputBackend, MouseCursor};
|
||||
use crate::backend::locale::LocaleBackend;
|
||||
|
@ -19,6 +19,7 @@ use crate::external::{ExternalInterface, ExternalInterfaceProvider};
|
|||
use crate::library::Library;
|
||||
use crate::loader::LoadManager;
|
||||
use crate::prelude::*;
|
||||
use crate::property_map::PropertyMap;
|
||||
use crate::tag_utils::SwfMovie;
|
||||
use crate::transform::TransformStack;
|
||||
use crate::vminterface::Instantiator;
|
||||
|
@ -312,13 +313,14 @@ impl Player {
|
|||
///
|
||||
/// 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) {
|
||||
pub fn fetch_root_movie(&mut self, movie_url: &str, parameters: PropertyMap<String>) {
|
||||
self.mutate_with_update_context(|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(),
|
||||
parameters,
|
||||
);
|
||||
|
||||
context.navigator.spawn_future(process);
|
||||
|
@ -351,7 +353,21 @@ impl Player {
|
|||
MovieClip::from_movie(context.gc_context, context.swf.clone()).into();
|
||||
|
||||
root.set_depth(context.gc_context, 0);
|
||||
root.post_instantiation(context, root, None, Instantiator::Movie, false);
|
||||
let flashvars = if !context.swf.parameters().is_empty() {
|
||||
let object = ScriptObject::object(context.gc_context, None);
|
||||
for (key, value) in context.swf.parameters().iter() {
|
||||
object.define_value(
|
||||
context.gc_context,
|
||||
key,
|
||||
AvmString::new(context.gc_context, value).into(),
|
||||
EnumSet::empty(),
|
||||
);
|
||||
}
|
||||
Some(object.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
root.post_instantiation(context, root, flashvars, Instantiator::Movie, false);
|
||||
root.set_name(context.gc_context, "");
|
||||
context.levels.insert(0, root);
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ use std::hash::{Hash, Hasher};
|
|||
type FnvIndexMap<K, V> = IndexMap<K, V, FnvBuildHasher>;
|
||||
|
||||
/// A map from property names to values.
|
||||
#[derive(Debug)]
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct PropertyMap<V>(FnvIndexMap<PropertyName, V>);
|
||||
|
||||
impl<V> PropertyMap<V> {
|
||||
|
@ -107,6 +107,10 @@ impl<V> PropertyMap<V> {
|
|||
self.0.shift_remove(&CaseInsensitiveStr(key))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<V: Collect> Collect for PropertyMap<V> {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::backend::navigator::url_from_relative_path;
|
||||
use crate::property_map::PropertyMap;
|
||||
use gc_arena::Collect;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
@ -21,6 +22,9 @@ pub struct SwfMovie {
|
|||
|
||||
/// The URL the SWF was downloaded from.
|
||||
url: Option<String>,
|
||||
|
||||
/// Any parameters provided when loading this movie (also known as 'flashvars')
|
||||
parameters: PropertyMap<String>,
|
||||
}
|
||||
|
||||
impl SwfMovie {
|
||||
|
@ -36,6 +40,7 @@ impl SwfMovie {
|
|||
},
|
||||
data: vec![],
|
||||
url: None,
|
||||
parameters: PropertyMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,6 +54,7 @@ impl SwfMovie {
|
|||
header: self.header.clone(),
|
||||
data,
|
||||
url: source.url.clone(),
|
||||
parameters: source.parameters.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +96,12 @@ impl SwfMovie {
|
|||
data
|
||||
};
|
||||
|
||||
Ok(Self { header, data, url })
|
||||
Ok(Self {
|
||||
header,
|
||||
data,
|
||||
url,
|
||||
parameters: PropertyMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn header(&self) -> &Header {
|
||||
|
@ -118,6 +129,14 @@ impl SwfMovie {
|
|||
pub fn url(&self) -> Option<&str> {
|
||||
self.url.as_deref()
|
||||
}
|
||||
|
||||
pub fn parameters(&self) -> &PropertyMap<String> {
|
||||
&self.parameters
|
||||
}
|
||||
|
||||
pub fn parameters_mut(&mut self) -> &mut PropertyMap<String> {
|
||||
&mut self.parameters
|
||||
}
|
||||
}
|
||||
|
||||
/// A shared-ownership reference to some portion of an SWF datastream.
|
||||
|
|
|
@ -17,7 +17,11 @@ module.exports = class RuffleEmbed extends RufflePlayer {
|
|||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.stream_swf_url(this.attributes.src.value);
|
||||
let parameters = null;
|
||||
if (this.attributes.flashvars) {
|
||||
parameters = this.attributes.flashvars.value;
|
||||
}
|
||||
this.stream_swf_url(this.attributes.src.value, parameters);
|
||||
}
|
||||
|
||||
get src() {
|
||||
|
@ -34,9 +38,12 @@ module.exports = class RuffleEmbed extends RufflePlayer {
|
|||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
super.attributeChangedCallback(name, oldValue, newValue);
|
||||
console.log(name + " " + oldValue + " " + newValue);
|
||||
if (this.isConnected && name === "src") {
|
||||
this.stream_swf_url(this.attributes.src.value);
|
||||
let parameters = null;
|
||||
if (this.attributes.flashvars) {
|
||||
parameters = this.attributes.flashvars.value;
|
||||
}
|
||||
this.stream_swf_url(this.attributes.src.value, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,16 @@ module.exports = class RuffleObject extends RufflePlayer {
|
|||
url = this.params.movie;
|
||||
}
|
||||
|
||||
let parameters = RuffleObject.find_case_insensitive(
|
||||
this.params,
|
||||
"flashvars",
|
||||
RuffleObject.find_case_insensitive(
|
||||
this.attributes,
|
||||
"flashvars",
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
if (url) {
|
||||
this.allow_script_access =
|
||||
allowScriptAccess &&
|
||||
|
@ -41,7 +51,7 @@ module.exports = class RuffleObject extends RufflePlayer {
|
|||
new URL(url, window.location.href).origin));
|
||||
|
||||
//Kick off the SWF download.
|
||||
this.stream_swf_url(url);
|
||||
this.stream_swf_url(url, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,23 @@ exports.FLASH_ACTIVEX_CLASSID = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
|
|||
|
||||
const DIMENSION_REGEX = /^\s*(\d+(\.\d+)?(%)?)/;
|
||||
|
||||
function sanitize_parameters(parameters) {
|
||||
if (parameters === null || parameters === undefined) {
|
||||
return {};
|
||||
}
|
||||
if (!(parameters instanceof URLSearchParams)) {
|
||||
parameters = new URLSearchParams(parameters);
|
||||
}
|
||||
const output = {};
|
||||
|
||||
for (const [key, value] of parameters) {
|
||||
// Every value must be type of string
|
||||
output[key] = value.toString();
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
exports.RufflePlayer = class RufflePlayer extends HTMLElement {
|
||||
constructor(...args) {
|
||||
let self = super(...args);
|
||||
|
@ -152,15 +169,18 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement {
|
|||
* being loaded, or any errors that happen loading it.
|
||||
*
|
||||
* @param {String} url The URL to stream.
|
||||
* @param {URLSearchParams|String|Object} [parameters] The parameters (also known as "flashvars") to load the movie with.
|
||||
* If it's a string, it will be decoded into an object.
|
||||
* If it's an object, every key and value must be a String.
|
||||
*/
|
||||
async stream_swf_url(url) {
|
||||
async stream_swf_url(url, parameters) {
|
||||
//TODO: Actually stream files...
|
||||
try {
|
||||
if (this.isConnected && !this.is_unused_fallback_object()) {
|
||||
console.log("Loading SWF file " + url);
|
||||
|
||||
await this.ensure_fresh_instance();
|
||||
this.instance.stream_from(url);
|
||||
this.instance.stream_from(url, sanitize_parameters(parameters));
|
||||
|
||||
if (this.play_button) {
|
||||
this.play_button.style.display = "block";
|
||||
|
@ -205,14 +225,20 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement {
|
|||
* the movie being loaded.
|
||||
*
|
||||
* @param {String} url The URL to stream.
|
||||
* @param {URLSearchParams|String|Object} [parameters] The parameters (also known as "flashvars") to load the movie with.
|
||||
* If it's a string, it will be decoded into an object.
|
||||
* If it's an object, every key and value must be a String.
|
||||
*/
|
||||
async play_swf_data(data) {
|
||||
async play_swf_data(data, parameters) {
|
||||
try {
|
||||
if (this.isConnected && !this.is_unused_fallback_object()) {
|
||||
console.log("Got SWF data");
|
||||
|
||||
await this.ensure_fresh_instance();
|
||||
this.instance.load_data(new Uint8Array(data));
|
||||
this.instance.load_data(
|
||||
new Uint8Array(data),
|
||||
sanitize_parameters(parameters)
|
||||
);
|
||||
console.log("New Ruffle instance created.");
|
||||
|
||||
if (this.play_button) {
|
||||
|
|
|
@ -24,6 +24,7 @@ use ruffle_core::events::MouseWheelDelta;
|
|||
use ruffle_core::external::{
|
||||
ExternalInterfaceMethod, ExternalInterfaceProvider, Value as ExternalValue, Value,
|
||||
};
|
||||
use ruffle_core::property_map::PropertyMap;
|
||||
use ruffle_core::tag_utils::SwfMovie;
|
||||
use ruffle_core::PlayerEvent;
|
||||
use ruffle_web_common::JsResult;
|
||||
|
@ -124,22 +125,32 @@ impl Ruffle {
|
|||
/// 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) {
|
||||
pub fn stream_from(&mut self, movie_url: &str, parameters: &JsValue) -> Result<(), JsValue> {
|
||||
INSTANCES.with(|instances| {
|
||||
let instances = instances.borrow();
|
||||
let instance = instances.get(self.0).unwrap().borrow();
|
||||
instance.core.lock().unwrap().fetch_root_movie(movie_url);
|
||||
});
|
||||
let mut parameters_to_load = PropertyMap::new();
|
||||
populate_movie_parameters(¶meters, &mut parameters_to_load)?;
|
||||
instance
|
||||
.core
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fetch_root_movie(movie_url, parameters_to_load);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
pub fn load_data(&mut self, swf_data: Uint8Array, parameters: &JsValue) -> 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))?
|
||||
let mut movie = SwfMovie::from_data(&data, None)
|
||||
.map_err(|e| format!("Error loading movie: {}", e))?;
|
||||
populate_movie_parameters(¶meters, movie.parameters_mut())?;
|
||||
movie
|
||||
});
|
||||
|
||||
INSTANCES.with(|instances| {
|
||||
|
@ -899,3 +910,18 @@ pub fn set_panic_handler() {
|
|||
}));
|
||||
});
|
||||
}
|
||||
|
||||
fn populate_movie_parameters(
|
||||
input: &JsValue,
|
||||
output: &mut PropertyMap<String>,
|
||||
) -> Result<(), JsValue> {
|
||||
let keys = js_sys::Reflect::own_keys(input)?;
|
||||
for key in keys.values() {
|
||||
let key = key?;
|
||||
let value = js_sys::Reflect::get(input, &key)?;
|
||||
if let (Some(key), Some(value)) = (key.as_string(), value.as_string()) {
|
||||
output.insert(&key, value, false);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue