avm2: Implement flash.media.ID3Info (#14916)
This commit is contained in:
parent
5b920b8447
commit
556d16302b
|
@ -2625,6 +2625,17 @@ dependencies = [
|
||||||
"objc2",
|
"objc2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "id3"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ba0a11a3cf6f08d58a5629531bdb4e7c3b8b595e9812a31a7058b1176c4631e"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.2",
|
||||||
|
"byteorder",
|
||||||
|
"flate2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ident_case"
|
name = "ident_case"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -4417,6 +4428,7 @@ dependencies = [
|
||||||
"fnv",
|
"fnv",
|
||||||
"futures",
|
"futures",
|
||||||
"hashbrown 0.14.3",
|
"hashbrown 0.14.3",
|
||||||
|
"id3",
|
||||||
"image",
|
"image",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"jpegxr",
|
"jpegxr",
|
||||||
|
|
|
@ -65,6 +65,7 @@ enum-map = "2.7.3"
|
||||||
ttf-parser = "0.20"
|
ttf-parser = "0.20"
|
||||||
num-bigint = "0.4"
|
num-bigint = "0.4"
|
||||||
unic-segment = "0.9.0"
|
unic-segment = "0.9.0"
|
||||||
|
id3 = "1.12.0"
|
||||||
|
|
||||||
[target.'cfg(not(target_family = "wasm"))'.dependencies.futures]
|
[target.'cfg(not(target_family = "wasm"))'.dependencies.futures]
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
|
|
|
@ -175,6 +175,7 @@ pub struct SystemClasses<'gc> {
|
||||||
pub avm1movie: ClassObject<'gc>,
|
pub avm1movie: ClassObject<'gc>,
|
||||||
pub focusevent: ClassObject<'gc>,
|
pub focusevent: ClassObject<'gc>,
|
||||||
pub dictionary: ClassObject<'gc>,
|
pub dictionary: ClassObject<'gc>,
|
||||||
|
pub id3info: ClassObject<'gc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> SystemClasses<'gc> {
|
impl<'gc> SystemClasses<'gc> {
|
||||||
|
@ -308,6 +309,7 @@ impl<'gc> SystemClasses<'gc> {
|
||||||
avm1movie: object,
|
avm1movie: object,
|
||||||
focusevent: object,
|
focusevent: object,
|
||||||
dictionary: object,
|
dictionary: object,
|
||||||
|
id3info: object,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -811,6 +813,7 @@ fn load_playerglobal<'gc>(
|
||||||
("flash.geom", "Rectangle", rectangle),
|
("flash.geom", "Rectangle", rectangle),
|
||||||
("flash.geom", "Transform", transform),
|
("flash.geom", "Transform", transform),
|
||||||
("flash.geom", "ColorTransform", colortransform),
|
("flash.geom", "ColorTransform", colortransform),
|
||||||
|
("flash.media", "ID3Info", id3info),
|
||||||
("flash.media", "SoundChannel", soundchannel),
|
("flash.media", "SoundChannel", soundchannel),
|
||||||
("flash.media", "SoundTransform", soundtransform),
|
("flash.media", "SoundTransform", soundtransform),
|
||||||
("flash.media", "Video", video),
|
("flash.media", "Video", video),
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package flash.media {
|
||||||
|
|
||||||
|
public final dynamic class ID3Info {
|
||||||
|
public var album:String;
|
||||||
|
public var artist:String;
|
||||||
|
public var comment:String;
|
||||||
|
public var genre:String;
|
||||||
|
public var songName:String;
|
||||||
|
public var track:String;
|
||||||
|
public var year:String;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ package flash.media {
|
||||||
public native function get isURLInaccessible():Boolean;
|
public native function get isURLInaccessible():Boolean;
|
||||||
public native function get url():String;
|
public native function get url():String;
|
||||||
public native function get length():Number;
|
public native function get length():Number;
|
||||||
|
public native function get id3():ID3Info;
|
||||||
public native function play(startTime:Number = 0, loops:int = 0, sndTransform:SoundTransform = null):SoundChannel;
|
public native function play(startTime:Number = 0, loops:int = 0, sndTransform:SoundTransform = null):SoundChannel;
|
||||||
public native function extract(target:ByteArray, length:Number, startPosition:Number = -1):Number;
|
public native function extract(target:ByteArray, length:Number, startPosition:Number = -1):Number;
|
||||||
public native function close():void;
|
public native function close():void;
|
||||||
|
|
|
@ -4,10 +4,12 @@ use crate::avm2::activation::Activation;
|
||||||
use crate::avm2::object::{Object, QueuedPlay, SoundChannelObject, TObject};
|
use crate::avm2::object::{Object, QueuedPlay, SoundChannelObject, TObject};
|
||||||
use crate::avm2::parameters::ParametersExt;
|
use crate::avm2::parameters::ParametersExt;
|
||||||
use crate::avm2::value::Value;
|
use crate::avm2::value::Value;
|
||||||
|
use crate::avm2::Avm2;
|
||||||
use crate::avm2::Error;
|
use crate::avm2::Error;
|
||||||
use crate::backend::navigator::Request;
|
use crate::backend::navigator::Request;
|
||||||
use crate::character::Character;
|
use crate::character::Character;
|
||||||
use crate::display_object::SoundTransform;
|
use crate::display_object::SoundTransform;
|
||||||
|
use crate::string::AvmString;
|
||||||
use crate::{avm2_stub_getter, avm2_stub_method};
|
use crate::{avm2_stub_getter, avm2_stub_method};
|
||||||
use swf::{SoundEvent, SoundInfo};
|
use swf::{SoundEvent, SoundInfo};
|
||||||
|
|
||||||
|
@ -280,6 +282,28 @@ pub fn load_compressed_data_from_byte_array<'gc>(
|
||||||
Error::RustError(format!("Failed to register sound from bytearray: {e:?}").into())
|
Error::RustError(format!("Failed to register sound from bytearray: {e:?}").into())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let progress_evt = activation
|
||||||
|
.avm2()
|
||||||
|
.classes()
|
||||||
|
.progressevent
|
||||||
|
.construct(
|
||||||
|
activation,
|
||||||
|
&[
|
||||||
|
"progress".into(),
|
||||||
|
false.into(),
|
||||||
|
false.into(),
|
||||||
|
bytes.len().into(),
|
||||||
|
bytes.len().into(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.map_err(|e| Error::AvmError(AvmString::new_utf8(activation.gc(), e.to_string()).into()))?;
|
||||||
|
|
||||||
|
Avm2::dispatch_event(&mut activation.context, progress_evt, this);
|
||||||
|
|
||||||
|
this.as_sound_object()
|
||||||
|
.unwrap()
|
||||||
|
.read_and_call_id3_event(activation, bytes);
|
||||||
|
|
||||||
this.as_sound_object()
|
this.as_sound_object()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_sound(&mut activation.context, handle)?;
|
.set_sound(&mut activation.context, handle)?;
|
||||||
|
@ -296,3 +320,16 @@ pub fn load_pcm_from_byte_array<'gc>(
|
||||||
avm2_stub_method!(activation, "flash.media.Sound", "loadPCMFromByteArray");
|
avm2_stub_method!(activation, "flash.media.Sound", "loadPCMFromByteArray");
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implements `Sound.id3`
|
||||||
|
pub fn get_id3<'gc>(
|
||||||
|
_activation: &mut Activation<'_, 'gc>,
|
||||||
|
this: Object<'gc>,
|
||||||
|
_args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
|
if let Some(id3) = this.as_sound_object().unwrap().id3() {
|
||||||
|
Ok(id3.into())
|
||||||
|
} else {
|
||||||
|
Ok(Value::Null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -241,6 +241,7 @@ include "flash/media/H264Profile.as"
|
||||||
include "flash/media/Microphone.as"
|
include "flash/media/Microphone.as"
|
||||||
include "flash/media/MicrophoneEnhancedMode.as"
|
include "flash/media/MicrophoneEnhancedMode.as"
|
||||||
include "flash/media/MicrophoneEnhancedOptions.as"
|
include "flash/media/MicrophoneEnhancedOptions.as"
|
||||||
|
include "flash/media/ID3Info.as"
|
||||||
include "flash/media/Sound.as"
|
include "flash/media/Sound.as"
|
||||||
include "flash/media/SoundCodec.as"
|
include "flash/media/SoundCodec.as"
|
||||||
include "flash/media/SoundChannel.as"
|
include "flash/media/SoundChannel.as"
|
||||||
|
|
|
@ -4,12 +4,16 @@ use crate::avm2::activation::Activation;
|
||||||
use crate::avm2::object::script_object::ScriptObjectData;
|
use crate::avm2::object::script_object::ScriptObjectData;
|
||||||
use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject};
|
use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject};
|
||||||
use crate::avm2::value::Value;
|
use crate::avm2::value::Value;
|
||||||
|
use crate::avm2::Avm2;
|
||||||
use crate::avm2::Error;
|
use crate::avm2::Error;
|
||||||
|
use crate::avm2::EventObject;
|
||||||
use crate::backend::audio::SoundHandle;
|
use crate::backend::audio::SoundHandle;
|
||||||
use crate::context::UpdateContext;
|
use crate::context::UpdateContext;
|
||||||
use crate::display_object::SoundTransform;
|
use crate::display_object::SoundTransform;
|
||||||
|
use crate::string::AvmString;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use gc_arena::{Collect, GcCell, GcWeakCell, Mutation};
|
use gc_arena::{Collect, GcCell, GcWeakCell, Mutation};
|
||||||
|
use id3::{Tag, TagLike};
|
||||||
use std::cell::{Ref, RefMut};
|
use std::cell::{Ref, RefMut};
|
||||||
use swf::SoundInfo;
|
use swf::SoundInfo;
|
||||||
|
|
||||||
|
@ -29,6 +33,7 @@ pub fn sound_allocator<'gc>(
|
||||||
sound_data: SoundData::NotLoaded {
|
sound_data: SoundData::NotLoaded {
|
||||||
queued_plays: Vec::new(),
|
queued_plays: Vec::new(),
|
||||||
},
|
},
|
||||||
|
id3: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.into())
|
.into())
|
||||||
|
@ -58,6 +63,9 @@ pub struct SoundObjectData<'gc> {
|
||||||
|
|
||||||
/// The sound this object holds.
|
/// The sound this object holds.
|
||||||
sound_data: SoundData<'gc>,
|
sound_data: SoundData<'gc>,
|
||||||
|
|
||||||
|
/// ID3Info Object
|
||||||
|
id3: Option<Object<'gc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Collect)]
|
#[derive(Collect)]
|
||||||
|
@ -129,6 +137,89 @@ impl<'gc> SoundObject<'gc> {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn id3(self) -> Option<Object<'gc>> {
|
||||||
|
let this = self.0.read();
|
||||||
|
this.id3
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_id3(self, mc: &Mutation<'gc>, id3: Option<Object<'gc>>) {
|
||||||
|
let mut this = self.0.write(mc);
|
||||||
|
this.id3 = id3;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_and_call_id3_event(self, activation: &mut Activation<'_, 'gc>, bytes: &[u8]) {
|
||||||
|
let id3 = activation
|
||||||
|
.avm2()
|
||||||
|
.classes()
|
||||||
|
.id3info
|
||||||
|
.construct(activation, &[])
|
||||||
|
.expect("failed to construct ID3Info object");
|
||||||
|
let tag = Tag::read_from(bytes);
|
||||||
|
if let Ok(ref tag) = tag {
|
||||||
|
if let Some(v) = tag.album() {
|
||||||
|
id3.set_public_property(
|
||||||
|
"album",
|
||||||
|
AvmString::new_utf8(activation.gc(), v).into(),
|
||||||
|
activation,
|
||||||
|
)
|
||||||
|
.expect("failed set_public_property");
|
||||||
|
}
|
||||||
|
if let Some(v) = tag.artist() {
|
||||||
|
id3.set_public_property(
|
||||||
|
"artist",
|
||||||
|
AvmString::new_utf8(activation.gc(), v).into(),
|
||||||
|
activation,
|
||||||
|
)
|
||||||
|
.expect("failed set_public_property");
|
||||||
|
}
|
||||||
|
if let Some(v) = tag.comments().next() {
|
||||||
|
id3.set_public_property(
|
||||||
|
"comment",
|
||||||
|
AvmString::new_utf8(activation.gc(), v.text.clone()).into(),
|
||||||
|
activation,
|
||||||
|
)
|
||||||
|
.expect("failed set_public_property");
|
||||||
|
}
|
||||||
|
if let Some(v) = tag.genre() {
|
||||||
|
id3.set_public_property(
|
||||||
|
"genre",
|
||||||
|
AvmString::new_utf8(activation.gc(), v).into(),
|
||||||
|
activation,
|
||||||
|
)
|
||||||
|
.expect("failed set_public_property");
|
||||||
|
}
|
||||||
|
if let Some(v) = tag.title() {
|
||||||
|
id3.set_public_property(
|
||||||
|
"songName",
|
||||||
|
AvmString::new_utf8(activation.gc(), v).into(),
|
||||||
|
activation,
|
||||||
|
)
|
||||||
|
.expect("failed set_public_property");
|
||||||
|
}
|
||||||
|
if let Some(v) = tag.track() {
|
||||||
|
id3.set_public_property(
|
||||||
|
"track",
|
||||||
|
AvmString::new_utf8(activation.gc(), v.to_string()).into(),
|
||||||
|
activation,
|
||||||
|
)
|
||||||
|
.expect("failed set_public_property");
|
||||||
|
}
|
||||||
|
if let Some(v) = tag.year() {
|
||||||
|
id3.set_public_property(
|
||||||
|
"year",
|
||||||
|
AvmString::new_utf8(activation.gc(), v.to_string()).into(),
|
||||||
|
activation,
|
||||||
|
)
|
||||||
|
.expect("failed set_public_property");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.set_id3(activation.context.gc_context, Some(id3));
|
||||||
|
if tag.is_ok() {
|
||||||
|
let id3_evt = EventObject::bare_default_event(&mut activation.context, "id3");
|
||||||
|
Avm2::dispatch_event(&mut activation.context, id3_evt, self.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the sound had a valid position, and `false` otherwise
|
/// Returns `true` if the sound had a valid position, and `false` otherwise
|
||||||
|
|
|
@ -1589,11 +1589,16 @@ impl<'gc> Loader<'gc> {
|
||||||
|
|
||||||
Avm2::dispatch_event(&mut activation.context, progress_evt, sound_object);
|
Avm2::dispatch_event(&mut activation.context, progress_evt, sound_object);
|
||||||
|
|
||||||
|
sound_object
|
||||||
|
.as_sound_object()
|
||||||
|
.expect("Not a sound object")
|
||||||
|
.read_and_call_id3_event(&mut activation, body.as_slice());
|
||||||
|
|
||||||
let complete_evt = Avm2EventObject::bare_default_event(
|
let complete_evt = Avm2EventObject::bare_default_event(
|
||||||
&mut activation.context,
|
&mut activation.context,
|
||||||
"complete",
|
"complete",
|
||||||
);
|
);
|
||||||
Avm2::dispatch_event(uc, complete_evt, sound_object);
|
Avm2::dispatch_event(&mut activation.context, complete_evt, sound_object);
|
||||||
}
|
}
|
||||||
Err(_err) => {
|
Err(_err) => {
|
||||||
// FIXME: Match the exact error message generated by Flash.
|
// FIXME: Match the exact error message generated by Flash.
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
id3event id3:[object ID3Info]
|
||||||
|
album:test album
|
||||||
|
artist:test artist
|
||||||
|
comment:test comment
|
||||||
|
genre:test genre
|
||||||
|
songName:test title
|
||||||
|
track:5555
|
||||||
|
year:9999
|
|
@ -0,0 +1,24 @@
|
||||||
|
package
|
||||||
|
{
|
||||||
|
import flash.display.Sprite;
|
||||||
|
import flash.events.Event;
|
||||||
|
import flash.media.Sound;
|
||||||
|
import flash.net.URLRequest;
|
||||||
|
|
||||||
|
public class Main extends Sprite
|
||||||
|
{
|
||||||
|
|
||||||
|
public function Main()
|
||||||
|
{
|
||||||
|
var sound:Sound = new Sound();
|
||||||
|
sound.addEventListener(Event.ID3, function(event:Event):void{
|
||||||
|
trace("id3event id3:" + sound.id3);
|
||||||
|
var properties:Array = ["album", "artist", "comment", "genre", "songName", "track", "year"];
|
||||||
|
for (var i:String in properties) trace(properties[i]+":"+sound.id3[properties[i]]);
|
||||||
|
});
|
||||||
|
sound.load(new URLRequest("test_audio.mp3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
num_frames = 1
|
Binary file not shown.
Loading…
Reference in New Issue