使用ActionScript 2.0或ActionScript 3.0处理音频文件的提示点(cue

阅读更多

译者:雪の猫

 

Macromedia Flash Professional 8 和 Adobe Flex Builder 2 都支持直接在Flash video (FLV)文件中使用提示点。使用Flash 8(ActionScript 2.0),完成这工作只需要一个FLVPlayback组件的实例和简单的设置。可以在组件检查面板中定义或者调用FLVPlayback.addAsCuePoint()方法;使用Flex Builder 2,需要的是对应VideoDisplay实例指定的CuePointManager类的实例。两种情况下,接下来都是设置一个cuePoint事件的监听器对象和定义该对象一个特定的响应函数。

 

而对音频文件,实现提示点要困难些。虽然使用Flash8能用如MediaController, MediaDisplay, 或MediaPlayback这些媒体组件来装载MP3文件,但是要注意这些组件是为了兼容Flash Player 6 and 7。这些组件的结构比FLVPlayback旧,这样,使用它们会使生成的swf文件多出至少55KB。使用FLVPlayback仅使之增加33KB,可是它不能装载MP3文件。类似的,Flex Builder 2的VideoDisplay类只能装载video。可以暂且做几个只有声音的FLV文件(不包含视频内容)。不过,这看来并不广泛适用。特别在一个团队中,你并不能决定选择哪些外部资源。而最通常的音频格式是MP3。幸运的是,用一点ActionScript就可以弥补这个不足了。

 

这篇文章将讲述一个特别解决方案的两个版本:用一个自定义类SoundSync来支持内部声音资源或者是外部的MP3文件。代码有ActionScript 2.0(Flash Professional 8 )和ActionScript 2.0(Flex Builder 2)两种。

 

不管使用那种语言,方法是一样的。ActionScript 2.0和ActionScript 3.0 都提供了Sound类来代表嵌入或装载的声音文件。两种语言都允许播放,从开头播放或从音频的某个时刻播放,暂停,循环。我们当然想不重造轮子!那些已经有的功能都是有用的。缺少的只是对提示点的支持,所以看来应该保留原有功能而extend这个Sound类。

 

自定义类SoundSync要继承Sound并添加下面的新公有方法:

addCuePoint(): 添加一个提示点

getCuePoint(): 通过提示点的名字或者时间返回该提示点

removeCuePoint(): 通过提示点的名字或者时间删除该提示点

removeAllCuePoints(): 删除所有的提示点

 

 

此外,还添加了几个私有方法。并根据使用语言不同重写一些原有的方法。简而言之,SoundSync类使用一个timer不断对照当前的播放时间检查当前提示点对象的time属性。我们先从ActionScript 2.0开始再移植到ActionScript 3.0。

 

需要环境

 

为了充分学习,需要如下软件和文件:

Flash Professional 8

 

Flex Builder 2

 

In Labs: Flash Professional 9 ActionScript 3.0 Preview (可选)

 

示例文件

sound_sync.zip (ZIP, 3MB)

 

准备知识

对ActionScript 2.0的中等掌握和对Flex Builder 2及ActionScript 3.0的介绍性了解。

 

 

ActionScript 2.0版的SoundSync类

 

自定义类必须保存到一个外部文本文件中,命名为SoundSync.as。按照最佳实践,应该把这个文件放在符合包结构的路径下的文件夹中。这里,应该放到相对于全局classpath设置的(选择Edit > Preferences > ActionScript category > ActionScript 2.0 Settings 按钮,中文的按钮……我不是中文版)路径为net/quip/sound这个文件夹下。为了方便按照我的域名给包起名为net.quip.sound。请随便把它改成你喜欢的吧。有关包的细节,请参考Flash8帮助文档中”……我又不是中文版–b”节。

 

创建ActionScript 2.0 类文件

启动Flash Professional 8,选择File > New > ActionScript File 图一

 ---

|图1    |

|    |

 ---

 

输入如下的ActionScript代码:

/**************代码*1***********/

import mx.events.EventDispatcher;

import mx.utils.Delegate;

class net.quip.sound.SoundSync extends Sound {

// PROPERTIES 属性

// CONSTRUCTOR 构造

// METHODS 方法

// EVENT HANDLERS 事件处理

}

 

 

头两行import了两个类,后面的代码会用到他们。在ActionScript 2.0中,import语句可以方便以后输入类名的时候不必再输入其完整的包名。

 

 

接下来,是声明这个类继承自基类Sound。然后写上属性,构造函数,方法和事件的注释占个位置。对一个类的代码,这些不是必须的,但是他们使类的结构一目了然。下面把细节添上。

 

声明属性:

在// PROPERTIES 属性 的注释下面输入:

 

private var _cuePoints:Array;

private var _currentCuePoint:Number;

private var _interval:Number;

private var _intervalDuration:Number;

private var _secondOffset:Number;

// Event dispatcher

public var dispatchEvent:Function;

public var addEventListener:Function;

private var removeEventListener:Function;

 

为了方便,私有变量的名字用下划线(_)开头。这样就容易辨认出这些变量只能用在该类的内部(即,类外不可以访问)。一会儿再解释每个属性。先注意最后三个,为了使用EventDispatcher,它们是必须的,EventDispatcher可以使这个类的实例发送事件给监听器,包括cuePoint事件。

 

构造函数

在// CONSTRUCTOR 构造 的注释下面输入:

 

public function SoundSync(target:MovieClip) {

super(target);

init();

}

 

这个函数使SoundSync可以像Sound一模一样的使用。super语句调用父类(基类)的构造函数,它接受一个可选的MovieClip引用作参数。因为有这个语句,基类在这里完成其初始化。然后是调用init()方法,进一步完成SoundSync的初始化,下面介绍它:

 

方法

 

这个类包含下面一些方法,我们从init开始讲解:

 

init()

 

在// METHODS 方法 的注释下面输入:

 

private function init():Void {

// Initialize properties 初始化属性

_cuePoints = new Array();

_currentCuePoint = 0;

_intervalDuration = 50;

_secondOffset = 0;

// Initialize class instance as valid event broadcaster

// 把类的实例作为EventDispatcher的广播者

EventDispatcher.initialize(this);

}

 

好了,我们给几个属性赋值为默认的值。cuePoints是一个数组对象,元素是控制点对象的引用,用currentCuePoint作数组当前的索引。_intervalDuration 属性定义检查的周期,就是两次检查音频当前位置的时间间隔,单位是毫秒。_secondOffset属性用来指定播放开始后多少秒才开始检查(默认是0)。

 

最后一行调用EventDispatcher.initialize()方法,参数是this,这样每一个SoundSync的实例都可以发送事件了。

 

addCuePoint()

 

可以添加addCuePoint()方法了. 在init()之后输入:

 

// Add Cue Point 添加提示点

public function addCuePoint(cuePointName:String, cuePointTime:Number):Void {

_cuePoints.push(

{

type: “cuePoint”,

name: cuePointName,

time: cuePointTime,

target: this

}

);

_cuePoints.sortOn(”time”, Array.NUMERIC);

}

 

这个方法需要两个参数:cuePointName 和 cuePointTime。分别表示一个提示点的名字name和时刻temporal position。然后我们把两个参数的值赋给一个object对象的两个属性,用 Array.push()方法添加到cuePoints数组;花括号{}是new Object()的一个简记。此外,每一个提示点对象还需要一个字符串类型的type属性。最后,提示点对象还要一个target属性来引用发送事件的那个SoundSync的实例(this)。

 

每当添加一个提示点对象,_cuePoints数组要根据cuePoint.time属性重新排列。这样保证了提示点位置的正确。

 

getCuePoint()

 

为了对称, 应该提供一个方法检索已经被添加的提示点。在addCuePoint()方法后输入:

 

// Get Cue Point 得到提示点

public function getCuePoint(nameOrTime:Object):Object {

var counter:Number = 0;

while (counter < _cuePoints.length) {

if (typeof(nameOrTime) == “string”) {

if (_cuePoints[counter].name == nameOrTime) {

return _cuePoints[counter];

}

} else if (typeof(nameOrTime) == “number”) {

if (_cuePoints[counter].time == nameOrTime) {

return _cuePoints[counter];

}

}

counter++;

}

return null;

}

 

这个方法需要一个参数,可以是提示点的名字或时刻,这意味着参数类型可能是字符串或者可能是数值型。为了应对两种可能,声明参数类型是Object。在方法内,定义一个计数器,用来遍历_cuePoints数组。–b 在while循环里边用typeof检查参数nameOrTime的类型,如果是字符串,nameOrTime 比照当前提示点的name属性;如果是数值型,比照提示点的time属性。因为计数器是递增的,函数返回的是第一个name属性或time属性匹配nameOrTime的提示点,即使有重复的话也是如此。如果没有匹配,返回null。

 

getCurrentCuePointIndex() 和 getNextCuePointIndex()

 

这两个方法是”幕后英雄”,它们只被该类内部的其他方法访问。它们返回当前的或者下一个提示点在_cuePoints数组中的位置(索引),这是用来移去当前的提示点并选定下一个提示点。

 

在getCuePoint() 方法之后输入:

 

// 得到当前提示点的索引

private function getCurrentCuePointIndex(cuePoint:Object):Number {

var counter:Number = 0;

while (counter < _cuePoints.length) {

if (_cuePoints[counter].name == cuePoint.name) {

return counter;

}

counter++;

}

return null;

}

// 得到下一个提示点的索引

private function getNextCuePointIndex(seconds:Number):Number {

seconds = (seconds) ? seconds : 0;

var counter:Number = 0;

while (counter < _cuePoints.length) {

if (_cuePoints[counter].time >= seconds * 1000) {

return counter;

}

counter++;

}

return null;

}

 

 

第一个方法,getCurrentCuePointIndex(),要求一个提示点对象作为参数。使用一个刚刚那样的循环,这个方法检查每一个_cuePoints数组中提示点的name属性是不是和参数的name匹配。如果是,返回匹配像的索引,否则返回null。

 

 

第二个方法, getNextCuePointIndex(), 有趣一些 O_O||| 。 这个方法是要通过指定的时间来确定那一个提示点是”下一个”。假设有三个提示点在10秒,20秒和30秒处。如果音频播放了22秒,下一个可用的提示点就是30秒处的那个——在这个假设中是第三个,索引是2(因为数组下标是0开头)。

 

这个方法会被该类中的start()和pollCuePoints()两个方法调用,在后边一点讨论。现在有两个必须要解决的小问题:

 

参数seconds很有可能不能被提供,那样函数的返回值会是null。然后提示点保存的time是以毫秒为单位的,而基类的那些方法都要求其参数的单位是秒。这两个问题都很简单。第一个,我们用条件运算符(?:)来看看seconds参数的值,如果是合法的把它赋值给自己,否则把它赋值为0。

 

第二个,还是while循环遍历_cuePoints数组。比较每一个提示点的time属性和1000与参数的乘积(因为1秒是1000毫秒)。因为循环从0开始,第一个大于等于给定提示点time属性值的提示点是”下一个”提示点。啊~~这一句要怎么翻

 

removeCuePoint() 和 removeAllCuePoints()

 

这两个方法用来删除某一个提示点或删除全部提示点。在getNextCuePointIndex()方法之后输入

// 删除提示点

public function removeCuePoint(cuePoint:Object):Void {

_cuePoints.splice(getCurrentCuePointIndex(cuePoint), 1);

}

// 删除所有提示点

public function removeAllCuePoints():Void {

_cuePoints = new Array();

}

 

方法removeCuePoint(), 需要一个提示点对象作为参数。调用Array.splice() 方法来删除_cuePoints数组中指定的元素。用getCurrentCuePointIndex()方法的返回值确定要删除元素的索引。因为只删除一个元素,Array.splice()的第二个参数应该传入1。

 

方法removeAllCuePoints(), 是简单的把_cuePoints替换成一个新的空数组.

 

重写start(), stop(), 和loadSound() 方法

 

基类Sound有两个方法可以开始播放:Sound.start() 和 Sound.loadSound()。不管用那一个,SoundSync 都要开始不断的轮询音频的位置,和每一个提示点的time属性比较。同样的,调用了Sound.stop() 之后轮询要停止。

 

为了实现这一点, SoundSync重写了这三个方法,调用基类的方法完成基本功能之外执行附加的命令。

 

在removeAllCuePoints() 方法之后输入:

 

// Start

public function start(secondOffset:Number, loops:Number):Void {

super.start(secondOffset, loops);

dispatchEvent({type:”onStart”, target:this});

// Reset current cue point

_secondOffset = secondOffset;

_currentCuePoint = getNextCuePointIndex(secondOffset);

// Poll for cue points

clearInterval(_interval);

_interval = setInterval(Delegate.create(this, pollCuePoints), _intervalDuration);

}

// Load Sound

public function loadSound(url:String, isStreaming:Boolean):Void {

super.loadSound(url, isStreaming);

clearInterval(_interval);

_interval = setInterval(Delegate.create(this, pollCuePoints), _intervalDuration);

}

// Stop

public function stop(linkageID:String):Void {

if (linkageID) {

super.stop(linkageID);

} else {

super.stop();

}

dispatchEvent({type:”onStop”, target:this});

// Kill polling

clearInterval(_interval);

}

 

一开始, start()方法调用super.start()并传入两个可选参数。然后,发送onStart事件,提示音频的播放已经开始了。

 

两个重要的私有属性在这里根据可选参数secondOffset被赋值。第一个_secondOffset只是简单的copy以备用。第二个_currentCuePoint,以secondOffset作为getNextCuePointIndex()的参数计算得到。请注意,这里secondOffset 是一个可选参数(可能是null),所以getNextCuePointIndex()的确应该做那一检查。

 

最后,如果setInterval()的循环已经在运行,要用clearInterval()取消并开始新的轮询。静态方法Delegate.create()让轮询的作用域是这个类本身而不是setInterval()这个函数。

 

当然,开发人员可能使用loadSound()而绕过了start()。这种情况SoundSync的loadSound()是调用super.loadSound()并传入其期望的参数,然后启动轮询。

 

要节约处理器周期,一个好办法是当调用stop()之后停止轮询。因为linkageID在基类Sound中是一个可选参数,SoundSync的该函数要检查这个参数是否存在再调用合适的super.stop()。之后发送一个 onStop 事件并取消轮询。

 

这个类的灵魂: pollCuePoints()

 

 

把前面的一切整合起来! 在stop() 方法之后输入:

 

private function pollCuePoints():Void {

// If current position is near the current cue point’s time …

 // 如果当前位置接近提示点的time

var time:Number = _cuePoints[_currentCuePoint].time;

var span:Number = (_cuePoints[_currentCuePoint + 1].time) ? _cuePoints[_currentCuePoint + 1].time : time + _intervalDuration * 2;

if (position >= time && position <= span) {

// Dispatch event 发送事件

dispatchEvent(_cuePoints[_currentCuePoint]);

// Advance to next cue point … 下一个

if (_currentCuePoint < _cuePoints.length) {

_currentCuePoint++;

} else {

_currentCuePoint = getNextCuePointIndex(_secondOffset);

}

}

}

 

pollCuePoints()方法有点繁重但是很容易理解。这个方法每50毫秒调用一次,检查正在播放的音频的Sound.position属性,以比较当前和下一个提示点对象的time属性。如果position大于等于当前的提示点的time,但是小于等于下一个提示点time,那就把当前的提示点作为事件发送出去。

 

 

注意两个局部变量:time(时间) 和 span (间隔)。第一个是当前提示点time属性的值。而第二个可能是下一个提示点time属性的值——如果当前提示点的后面还有另外一个——或者当前提示点的time值加上轮询时间间隔的两倍。这个乘法是武断的,不过事实证明还很好用,何况只有到数组的最后一个提示点的时候才会用到。

 

cuePoint事件一发出,如果还有更多的提示点的话,_currentCuePoint就加1。没有了的话,音频就播放到结束,可能又要接着从头播放了,这样_currentCuePoint又要根据secondOffset被重置为最开始的那个提示点。

 

利用基类的事件处理

 

即使没有直接调用stop(),音频自己也会停止。这时轮询应该停止,请在// EVENT HANDLERS 事件处理 的注释后面写:

 

// onSoundComplete

public function onSoundComplete():Void {

// Kill polling

clearInterval(_interval);

// Reset current cue point

_currentCuePoint = 0;

// Dispatch event

dispatchEvent({type:”onSoundComplete”, target:this});

}

 

不幸的是,这样占去了一个Sound类很有用的事件(在AS2.0中有两种”事件处理”一种是比如onEnterFrame的回调函数,另一种是EventDispatcher这样使用AddEventListener注册监听器来处理的事件,这里的onSoundComplete函数是前者,而后面的dispatchEvent({type:”onSoundComplete”, target:this});属于后者,原文并没有区分,翻译的人解释道)。作为一个解决办法,SoundSync在clearInterval()中断轮询,_currentCuePoint重置为0之后,发送一个onSoundComplete来替代。这样一来,还是在音频播放完后触发动作的。

 

使用ActionScript 2.0版本的SoundSync

 

SoundSync 类使用简单。在需要音频提示点的FLA文件或其他ActionScript类中,实例化SoundSync来代替Sound类。之后要调用addCuePoint(),并创建监听对象来处理cuePoint 事件:

 

import net.quip.sound.SoundSync;

 

var ss:SoundSync = new SoundSync();

ss.addCuePoint(”first”, 1000);

ss.addCuePoint(”second”, 2000);

ss.addCuePoint(”third”, 3000);

ss.loadSound(”sample.mp3″, true);

 

var listener:Object = new Object();

listener.cuePoint = function(evt:Object):Void {

trace(evt.name + “, ” + evt.time);

}

ss.addEventListener(”cuePoint”, listener);

 

 

观看基于timeline的高级演示,请解压文章附带的ZIP文件,打开green_presidents.fla 。确认附带的音频文件green_presidents.mp3在和FLA文件相同的文件加下。并确认ActionScript 2.0版的SoundSync.as文件在相对全局classpath设置的目录为net/quip/sound文件夹下。

 

ActionScript 3.0的SoundSync

 

啊!准备好再来一次了吗?SoundSync 的ActionScript 3.0 版和其ActionScript2.0版非常接近。当然有一点差异,但是并不吓人。

 

创建ActionScript 3.0 的类文件

 

启动Flex Builder 2 菜单选择 File > New > ActionScript Project 见图2

 ---

|图2    |

|    |

 ---

 

给工程起名叫SoundSyncExample 然后点Finish(完成)按钮。这样就用 Flex Builder 2创建了一个新工程,工程包括bin和html-template文件夹,Flex和一个新的SoundSyncExample.as类文件将在发布时用到这两个文件夹,而SoundSyncExample将作为新应用程序的入口。SoundSyncExample将实例化ActionScript 3.0 版的S SoundSync;这对应的是上一节”使用 ActionScript 2.0 “的Flash 8的桢代码

 

看到 Flex Builder 2 自动的生成了包和类的声明,见图三,我们一会来添加内容。暂时保存和关闭它。

 

 ---

|图3    |

|    |

 ---

 

 

在工程中创建net.quip.sound 包的文件夹,选择File > New > Folder 。在New Folder(新文件夹)对话框,选择SoundSyncExample 文件夹在Folder Name(文件夹名)文本框中输入路径net/quip/sound。

 

最后,选择File > New > ActionScript Class。在New ActionScript Class(新ActionScript类) 对话框中,确认Package 文本框中是net.quip.sound 。在Name 项中输入SoundSync并在Superclass 项中输入flash.media.Sound 。选中Generate Constructor from Superclass (由父类生成构造函数)选项。见图4

 

 ---

|图4    |

|    |

 ---

 

和前一版一样,SoundSync 继承Sound 类。点 Finish(完成) 按钮。看到Flex Builder 2 又自动的生成了包和类的声明,并在SoundSync的构造函数中调用了super。

 

在ActionScript 3.0中,import语句不仅是为了方便,它们是必须的。作为代码自动生成功能的一部分,Flex Builder 2 会自动写好需要的import语句。不过,为了完成教程的学习,请把代码以及注释手工的添加到相应的位置。

 

package net.quip.sound

{

import flash.events.Event;

import flash.events.TimerEvent;

import flash.media.Sound;

import flash.media.SoundChannel;

import flash.media.SoundLoaderContext;

import flash.media.SoundTransform;

import flash.net.URLRequest;

import flash.utils.Timer;

 

public class SoundSync extends Sound

{

// PROPERTIES

// CONSTRUCTOR

public function SoundSync(stream:URLRequest=null, context:SoundLoaderContext=null)

{

super(stream, context);

init();

}

// METHODS

// EVENT HANDLERS

}

}

 

不要忘记构造函数中是要调用init()的!

 

声明属性

 

在// PROPERTIES 注释之后输入:

 

private var _cuePoints:Array;

private var _currentCuePoint:uint;

private var _timer:Timer;

private var _timerInterval:uint;

private var _startTime:Number;

private var _loops:uint;

private var _soundChannel:SoundChannel;

 

这里 _cuePoints 和 _currentCuePoint 与ActionScript 2.0版的那两个完全一样。 _timer 和 _timerInterval 属性 对应于那一版的_interval 和 _intervalDuration,而_startTime对应于之前的_secondOffset。最后,_loops 和 _soundChannel 是新的属性,将在后面逐步讲解。

 

注意到一些数值属性是新的数据类型uint。在ActionScript 3.0里,我们已经熟悉的Number表示的是双精度浮点数,表示Number要使用多达53???位 -_o。新的int类型是32为有符号整数(正数或者负数)而uint是32位无符号整数(只有正数)。Flash Player 处理int和uint比Number更加有效,所以如果不是必须使用浮点数应该选择新的数据类型。

 

构造函数

 

 

构造函数已经更新过了, 但是还是来回顾一下:

 

// CONSTRUCTOR

public function SoundSync(stream:URLRequest = null, context:SoundLoaderContext = null) {

super(stream, context);

init();

}

 

 

看起来很熟悉吧,因为这个和ActionScript 2.0版本非常相似。和以前一样,在父类初始化之后进行了这个类自己的初始化。不过注意到,在 ActionScript 3.0中可选的参数要给定一个默认值(这里两个都是null)

 

方法

 

大多数都熟悉了.

 

init()

在 // METHODS 注释之后输入:

 

// init

private function init():void {

_cuePoints = new Array();

_currentCuePoint = 0;

_timerInterval = 50;

_startTime = 0.0;

}

 

在这里用默认值进行了一些私有属性的初始化。注意函数返回类型的那个小写的”v”,区别于ActionScript 2.0中Void类型的大写V

 

addCuePoint()

 

这个方法是离开ActionScript 2.0的第一个里程碑 –b要怎么翻这句。在ActionScript 3.0 事件处理进行了很大变动,变得一致,易于控制和使用。为了通过类型检查,必须把_cuePoints数组中的一般Object对象替换成一个自定义类型CuePointEvent的对象,而CuePointEvent继承自新的Event类。

 

选择File > New > ActionScript Class。在New ActionScript Class (新ActionScript类)对话框,确认Package (包)文本框是 net.quip.sound;在Name(类名) 文本框中 输入CuePointEvent 并在Superclass(父类) 文本框中输入Event。选中Generate Constructor from Superclass (由父类生成构造函数)选项然后点Finish(完成) 按钮。把代码修改成和下面的一致:

 

package net.quip.sound

{

import flash.events.Event;

 

public class CuePointEvent extends Event

{

// PROPERTIES

public static const CUE_POINT:String = “cuePoint”;

public var name:String;

public var time:uint;

 

// CONSTRUCTOR

public function CuePointEvent(type:String, cuePointName:String, cuePointTime:uint, bubbles:Boolean = false, cancelable:Boolean = false)

{

super(type, bubbles, cancelable);

this.name = cuePointName;

this.time = cuePointTime;

}

 

// METHODS

// Clone

public override function clone():Event

{

return new CuePointEvent(type, name, time, bubbles, cancelable);

}

}

}

 

这个类有三个公有属性,其中第一个是常量:字符串”cuePoint”。使用这个属性的原因是,按照最佳实践推荐,这样做使事件的类型可以在外部的代码中使用CuePointEvent.CUE_POINT而不是字符串来指定。第二个和第三个属性用于保存提示点的name和time。

 

修改完代码之后,留意一下构造函数的参数。CuePointEvent 需要type, cuePointName 和 cuePointTime 然后是一组用于Event类的可选参数。调用super的时候按照Event的构造函数的需要提供参数。其他的赋值给对应的属性。

 

最后重写Event。clone()方法来返回一个派生类CuePointEvent的实例而不是原本的Event类的。

 

现在要写SoundSync.addCuePoint()方法了。回到SouncSync.as文档并在其init()方法之后输入:

 

// Add Cue Point

public function addCuePoint(cuePointName:String, cuePointTime:uint):void {

_cuePoints.push(new CuePointEvent(CuePointEvent.CUE_POINT, cuePointName, cuePointTime));

_cuePoints.sortOn(”time”, Array.NUMERIC);

}

 

除了要求使用Event的派生类,这个方法和ActionScript 2.0 版的几乎一样。

 

 

getCuePoint()

 

比起之前的版本只有一个变化是局部变量的计数器使用uint类型代替Number类型:

 

// Get Cue Point

public function getCuePoint(nameOrTime:Object):Object {

var counter:uint = 0;

while (counter < _cuePoints.length) {

if (typeof(nameOrTime) == “string”) {

if (_cuePoints[counter].name == nameOrTime) {

return _cuePoints[counter];

}

} else if (typeof(nameOrTime) == “number”) {

if (_cuePoints[counter].time == nameOrTime) {

return _cuePoints[counter];

}

}

counter++;

}

return null;

}

 

 

更多的相同

 

下面的四个方法getCurrentCuePointIndex(), getNextCuePointIndex(), removeCuePoint()
和removeAllCuePoints()。这些只是代码移植中非常一般的变化。一些原来是Number类型的用uint替换,Void换成void然后解决getNextCuePoint() 方法中两个小问题的办法有一些变化。这一次传入的参数已经是以毫秒为单位的了然后要用isNaN()函数来检查可能传进的null值,因为ActionScript3.0对数值型的检查更加严格。

 

在 getCuePoint() 方法之后输入:

// Get Current Cue Point Index

private function getCurrentCuePointIndex(cuePoint:CuePointEvent):uint {

var counter:uint = 0;

while (counter < _cuePoints.length) {

if (_cuePoints[counter].name == cuePoint.name) {

return counter;

}

counter++;

}

return null;

}

// Get Next Cue Point Index

private function getNextCuePointIndex(milliseconds:Number):uint {

if (isNaN(milliseconds)) {

milliseconds = 0;

}

var counter:uint = 0;

while (counter < _cuePoints.length) {

if (_cuePoints[counter].time >= milliseconds) {

return counter;

}

counter++;

}

return null;

}

// Remove Cue Point

public function removeCuePoint(cuePoint:CuePointEvent):void {

_cuePoints.splice(getCurrentCuePointIndex(cuePoint), 1);

}

// Remove All Cue Points

public function removeAllCuePoints():void {

_cuePoints = new Array();

}

 

 

play()

 

和原来的类有两个主要的不同。在ActionScript 3.0中,既没有start() 也没有loadSound() 方法。最接近的是play() 和 load()。两者中,只有前者能开始播放音频。这就是说只要重写play()方法就够了,但是要多解释一下。

 

在removeAllCuePoints() 方法之后输入:

 

// Play

public override function play(startTime:Number = 0.0, loops:int=0, sndTransform:SoundTransform=null):SoundChannel {

_soundChannel = super.play(startTime, loops, sndTransform);

_soundChannel.addEventListener(Event.SOUND_COMPLETE, onSoundComplete);

dispatchEvent(new Event(”play”));

// Reset current cue point

_startTime = startTime;

_loops = 0;

_currentCuePoint = getNextCuePointIndex(startTime);

// Poll for cue points

_timer = new Timer(_timerInterval);

_timer.addEventListener(TimerEvent.TIMER, pollCuePoints);

_timer.start();

return _soundChannel;

}

 

注意函数声明中的override关键字,这里是必须的。在 ActionScript 3.0中Sound.play()方法返回一个SoundChannel 类的实例,使用它来控制正在播放音频的声音效果。自定义类SoundSync的私有成员 _soundChannel 将引用这个实例,然后立刻使用它来监听表示播放完毕的soundComplete事件。这样也使在合适时取消轮询变得可能。

 

发送一个自定义的play事件。接着,保存一下 play()方法的两个可选参数startTime和loops以备用。事实上,私有属性_loops并不总和loops参数一致,后者表示了音频应该重复的次数。在这个类中,_loops会被 pollCuePoints() 累加,_loops是为了完成一些必须的算法。在ActionScript 2.0里,当循环的音频播放完毕而重复时Sound.position属性会置0,这样就方便了重置提示点。在ActionScript 3.0里,对应的属性是SoundChannel.position,可是SoundChannel.position不会自动置0。于是,当音频需要重复是_loops就被设计用来乘以SoundChannel.position的值。

 

然后,_currentCuePoint属性和之前的一样由 getNextCuePointIndex()来设定。使用新的Timer类代替setInterval()函数来实现轮询。这里的_timer保存一个对Timer 类实例的引用并负责监听TimerEvent.TIMER事件来触发pollCuePoints()方法。最后返回_soundChannel满足重写的Sound.play()方法。

 

stop()

 

和原来的版本类似,这个方法在明确被调用而停止音频的播放时取消轮询。注意这个方法并不是override的, 因为ActionScript 3.0中停止一个声音是由SoundChannel类控制的。在play()方法的下面输入:

 

// Stop

public function stop():void {

_soundChannel.stop();

dispatchEvent(new Event(”stop”));

// Kill polling

_timer.stop();

}

 

 

pollCuePoints()

 

ActionScript 3.0 中的”类的灵魂”和ActionScript 2.0的非常类似。记住SoundChannel.position对循环播放的音频并不会置0,所以如果循环播放要用内部的 _loops 属性乘以SoundChannel.position。此外的都是以前一样:检查当前提示点和下一个提示点的时间属性。如果音频的position 在当前提示点和下一个提示点之间,发送cuePoint事件。当没有下一个提示点时,检查一个武断的值,它是当前提示点的time值加上轮询时间间隔的两倍。

 

在stop()方法之后输入:

 

// Poll Cue Points

private function pollCuePoints(event:TimerEvent):void {

var time:Number = _cuePoints[_currentCuePoint].time + (length * _loops);

var span:Number = 0;

if (_cuePoints[_currentCuePoint + 1] == undefined) {

span = time + _timerInterval * 2;

} else {

span = _cuePoints[_currentCuePoint + 1].time + (length * _loops);

};

if (_soundChannel.position >= time && _soundChannel.position <= span) {

// Dispatch event

dispatchEvent(_cuePoints[_currentCuePoint]);

// Advance to next cue point …

if (_currentCuePoint < _cuePoints.length - 1) {

_currentCuePoint++;

} else {

_currentCuePoint = getNextCuePointIndex(_startTime);

_loops++;

}

}

}

 

利用同样的基类事件

 

和以前一样,要在音频文件自然结束时取消轮询。并发送soundComplete 来弥补已经用掉了一个。

 

在// EVENT HANDLERS 注释之后输入:

 

// onSoundComplete

public function onSoundComplete(event:Event):void {

// Reset current cue point

_currentCuePoint = 0;

// Kill polling

_timer.stop();

// Dispatch event

dispatchEvent(new Event(Event.SOUND_COMPLETE));

}

 

使用ActionScript 3.0版本的SoundSync

 

还记得SoundSyncExample.as文件吧? 现在该打开它了。

 

把代码改成:

 

package {

import flash.display.Sprite;

import flash.events.Event;

import flash.net.URLRequest;

 

import net.quip.sound.CuePointEvent;

import net.quip.sound.SoundSync;

 

public class SoundSyncExample extends Sprite

{

// CONSTRUCTOR

public function SoundSyncExample()

{

var ss:SoundSync = new SoundSync();

ss.load(new URLRequest(”green_presidents.mp3″));

ss.addCuePoint(”Benjamin Franklin”, 20100);

ss.addCuePoint(”Ulysses S. Grant”, 16479);

ss.addCuePoint(”Alexander Hamilton”, 9431);

ss.addCuePoint(”Andrew Jackson”, 13278);

ss.addCuePoint(”Thomas Jefferson”, 2439);

ss.addCuePoint(”Abraham Lincoln”, 5480);

ss.addCuePoint(”George Washington”, 0);

ss.load(new URLRequest(”green_presidents.mp3″));

ss.play();

ss.addEventListener(CuePointEvent.CUE_POINT, onCuePoint);

ss.addEventListener(Event.SOUND_COMPLETE, onSoundComplete);

}

// On Cue Point

private function onCuePoint(event:CuePointEvent):void {

trace(”Cue point: ” + event.name + “, ” + event.time);

}

// On Sound Complete

private function onSoundComplete(event:Event):void {

trace(”Audio finished: ” + event.type);

}

}

}

 

解压文章附带的ZIP文件后,把green_presidents.mp3放在Flex Builder 2 工程的文件夹下。以windows XP为例,默认的路径是C:\Documents and Settings\<用户名>\My Documents\Flex Builder 2\<工程名>

 

选择Run > Debug SoundSyncExample。 将启动默认浏览器来播放包含应用程序的SWF文件。当SWF文件在浏览器中运行时,切换到Flex Builder 2 观察Console (控制台)面板,当达到提示点时发现trace()的输出。看,虽然这几为总统先生按照姓氏字母的先后被排近来,它们还是按照时间顺序被触发。

 

如果有Flash Professional 9 ActionScript 3.0 Preview public alpha 发行版 –b,也可以用来测试。确认全局classpath设置指向含有package(net/quip/sound)的那个文件夹,然后打开一个新的FLA文件。确认发布设置是 ActionScript 3.0 然后设置Document Class(文档类)文本框中是SoundSyncExample。保存FLA文件到Flex Builder 2 文件夹的SoundSyncExample的子文件夹。测试SWF文件,注意观察Ouput(输出)面版。

 

进一步的学习

 

在本教程中讲述了使用ActionScript 2.0 及 3.0创建自定义SoundSync类。代码分别使用良种语言扩展了内建的类Sound,向其添加了提示点功能来回放音频文件。教程介绍了ActionScript两个版本的一些不同,有新的数据类型和相似的对象的结构上的变化。

 

代码升级/移植是叫人难受的,但是我希望这个练习可以减少些盲目的恐惧。ActionScript 3.0的前景被大大的拓宽了,但是它还是ActionScript。伴随你的足迹,探险会更激动人心。

 

学习更多关于 ActionScript 2.0,请参考下面的资源:

 

Learning ActionScript 2.0 in Flash (LiveDocs)

ActionScript 2.0 best practices

Flash ActionScript 2.0 Language Reference (LiveDocs)

Debugging ActionScript 2.0 code: Lifting the blindfold

 

学习更多关于 ActionScript 3.0, 包括移植到新语言的技巧, 请参考下面的资源:

 

ActionScript 3.0 overview (LiveDocs)

Tips for learning ActionScript 3.0

ActionScript 2.0 migration (LiveDocs)

Programming ActionScript 3.0 (LiveDocs)

Flex 2 Language Reference (LiveDocs)

 

关于作者

 

David Stiller是一位职业多媒体程序员/设计师。曾在NASA,Adobe和美国其他的主要汽车制造及造船公司任职。他喜欢立体摄影艺术,精致的木玩具船,还有土耳其咖啡。David自学成材,乐于和人分享在Flash 及ActionScript 论坛上学习,讨论和帮助的快乐。他是Community MX站点的驻站作者,该站是一个针对adobe产品的开发培训网站。David现在和他迷人的妻子Dawn还有可爱的女儿Meridian居住在维吉尼亚。

你可能感兴趣的:(ActionScript,Flex,Flash,.net,Adobe)