avm2: Implement flash.media.ID3Info (#14916)

This commit is contained in:
Lv Yitian 2024-03-03 03:13:31 +08:00 committed by GitHub
parent 5b920b8447
commit 556d16302b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 197 additions and 1 deletions

12
Cargo.lock generated
View File

@ -2625,6 +2625,17 @@ dependencies = [
"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]]
name = "ident_case"
version = "1.0.1"
@ -4417,6 +4428,7 @@ dependencies = [
"fnv",
"futures",
"hashbrown 0.14.3",
"id3",
"image",
"indexmap",
"jpegxr",

View File

@ -65,6 +65,7 @@ enum-map = "2.7.3"
ttf-parser = "0.20"
num-bigint = "0.4"
unic-segment = "0.9.0"
id3 = "1.12.0"
[target.'cfg(not(target_family = "wasm"))'.dependencies.futures]
version = "0.3.30"

View File

@ -175,6 +175,7 @@ pub struct SystemClasses<'gc> {
pub avm1movie: ClassObject<'gc>,
pub focusevent: ClassObject<'gc>,
pub dictionary: ClassObject<'gc>,
pub id3info: ClassObject<'gc>,
}
impl<'gc> SystemClasses<'gc> {
@ -308,6 +309,7 @@ impl<'gc> SystemClasses<'gc> {
avm1movie: object,
focusevent: object,
dictionary: object,
id3info: object,
}
}
}
@ -811,6 +813,7 @@ fn load_playerglobal<'gc>(
("flash.geom", "Rectangle", rectangle),
("flash.geom", "Transform", transform),
("flash.geom", "ColorTransform", colortransform),
("flash.media", "ID3Info", id3info),
("flash.media", "SoundChannel", soundchannel),
("flash.media", "SoundTransform", soundtransform),
("flash.media", "Video", video),

View File

@ -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;
}
}

View File

@ -17,6 +17,7 @@ package flash.media {
public native function get isURLInaccessible():Boolean;
public native function get url():String;
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 extract(target:ByteArray, length:Number, startPosition:Number = -1):Number;
public native function close():void;

View File

@ -4,10 +4,12 @@ use crate::avm2::activation::Activation;
use crate::avm2::object::{Object, QueuedPlay, SoundChannelObject, TObject};
use crate::avm2::parameters::ParametersExt;
use crate::avm2::value::Value;
use crate::avm2::Avm2;
use crate::avm2::Error;
use crate::backend::navigator::Request;
use crate::character::Character;
use crate::display_object::SoundTransform;
use crate::string::AvmString;
use crate::{avm2_stub_getter, avm2_stub_method};
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())
})?;
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()
.unwrap()
.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");
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)
}
}

View File

@ -241,6 +241,7 @@ include "flash/media/H264Profile.as"
include "flash/media/Microphone.as"
include "flash/media/MicrophoneEnhancedMode.as"
include "flash/media/MicrophoneEnhancedOptions.as"
include "flash/media/ID3Info.as"
include "flash/media/Sound.as"
include "flash/media/SoundCodec.as"
include "flash/media/SoundChannel.as"

View File

@ -4,12 +4,16 @@ use crate::avm2::activation::Activation;
use crate::avm2::object::script_object::ScriptObjectData;
use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject};
use crate::avm2::value::Value;
use crate::avm2::Avm2;
use crate::avm2::Error;
use crate::avm2::EventObject;
use crate::backend::audio::SoundHandle;
use crate::context::UpdateContext;
use crate::display_object::SoundTransform;
use crate::string::AvmString;
use core::fmt;
use gc_arena::{Collect, GcCell, GcWeakCell, Mutation};
use id3::{Tag, TagLike};
use std::cell::{Ref, RefMut};
use swf::SoundInfo;
@ -29,6 +33,7 @@ pub fn sound_allocator<'gc>(
sound_data: SoundData::NotLoaded {
queued_plays: Vec::new(),
},
id3: None,
},
))
.into())
@ -58,6 +63,9 @@ pub struct SoundObjectData<'gc> {
/// The sound this object holds.
sound_data: SoundData<'gc>,
/// ID3Info Object
id3: Option<Object<'gc>>,
}
#[derive(Collect)]
@ -129,6 +137,89 @@ impl<'gc> SoundObject<'gc> {
}
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

View File

@ -1589,11 +1589,16 @@ impl<'gc> Loader<'gc> {
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(
&mut activation.context,
"complete",
);
Avm2::dispatch_event(uc, complete_evt, sound_object);
Avm2::dispatch_event(&mut activation.context, complete_evt, sound_object);
}
Err(_err) => {
// FIXME: Match the exact error message generated by Flash.

View File

@ -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

24
tests/tests/swfs/avm2/id3_info/test.as vendored Normal file
View File

@ -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"));
}
}
}

BIN
tests/tests/swfs/avm2/id3_info/test.swf vendored Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
num_frames = 1

Binary file not shown.