以下是一个功能齐全的 AIR 纯 actionscript 项目
运行时,尝试打开一个大的 FLV(我正在使用 3GB 文件对其进行测试)
将 DEBUG_UNUSED_BUFFER 和 DEBUG_APPEND_VIDEO 设置为 false,它可以正常工作 - 读取整个文件没有问题。
但是,将其中任何一个设置为 true 时,它都会因 OUT OF MEMORY 错误而崩溃。
出于实际目的,我对 appendBytes() 失败的原因更感兴趣,但为了感兴趣,DEBUG_UNUSED_BUFFER 仅使其喜欢文件的 6%,而 DEBUG_APPEND_VIDEO 使其达到大约 46% 左右。
问题:那么我们应该如何播放大视频?!
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.MouseEvent;
import flash.events.ProgressEvent;
import flash.events.TimerEvent;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.net.FileFilter;
import flash.net.NetConnection;
import flash.net.NetStream;
import flash.net.NetStreamAppendBytesAction;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import flash.utils.ByteArray;
import flash.utils.Timer;
public class MEMORY_TEST extends Sprite
{
//Set this to throttle data processing to once every DEBUG_THROTTLE_TIME milliseconds
// 0 = no throttling at all
// Note this this seems to make little difference, other than making it easier to see what's happening
private static const DEBUG_THROTTLE_TIME:Number = 100;
//Set this to write all bytes to an unused buffer.
//THIS FAILS (at around 237912064 bytes)!!!!!
private static const DEBUG_UNUSED_BUFFER:Boolean = false;
//Set this to write the video data via appendBytes.
//THIS FAILS (at around 1360003072 bytes)!!!!
private static const DEBUG_APPEND_VIDEO:Boolean = true;
/****************************************************************/
/******* Nothing else to configure below this line **************/
/****************************************************************/
private var openButton:Sprite;
private var statusTextField:TextField;
private var inputFile:File = null;
private var inputFileStream:FileStream = null;
private var netStream:NetStream = null;
private var netConnection:NetConnection = null;
private var readBytes:ByteArray = null;
private var totalBytesRead:Number = 0;
private var throttleTimer:Timer = null;
private var unusedBuffer:ByteArray = null;
private static const READSIZE:uint = 2048;
public function MEMORY_TEST()
{
this.addEventListener(Event.ADDED_TO_STAGE, onStage);
}
/*************************
*
* UI SETUP
*
**************************/
private function onStage(evt:Event) {
this.removeEventListener(Event.ADDED_TO_STAGE, onStage);
makeButtonAndStatus();
updateStatus('Click the button to begin');
}
private function makeButtonAndStatus(buttonText:String = 'Open File') {
var textField:TextField = new TextField();
var fmt:TextFormat = new TextFormat();
var padding:Number = 20;
var halfPadding:Number = padding/2;
//Button
fmt.color = 0xFFFFFF;
fmt.size = 24;
fmt.font = "_sans";
fmt.align = TextFormatAlign.LEFT;
textField.autoSize = TextFieldAutoSize.LEFT;
textField.multiline = false;
textField.wordWrap = false;
textField.defaultTextFormat = fmt;
textField.text = buttonText;
openButton = new Sprite();
openButton.graphics.beginFill(0x0B8CC3);
openButton.graphics.drawRoundRect(-halfPadding,-halfPadding,textField.width + padding, textField.height + padding, 20, 20);
openButton.graphics.endFill();
openButton.addChild(textField);
openButton.buttonMode = true;
openButton.useHandCursor = true;
openButton.mouseChildren = false;
openButton.addEventListener(MouseEvent.CLICK, selectFile);
openButton.x = (stage.stageWidth - openButton.width)/2;
openButton.y = (stage.stageHeight - openButton.height)/2;
addChild(openButton);
//Status
statusTextField = new TextField();
fmt = new TextFormat();
fmt.color = 0xFF0000;
fmt.size = 17;
fmt.font = "_sans";
fmt.align = TextFormatAlign.CENTER;
statusTextField.defaultTextFormat = fmt;
statusTextField.multiline = true;
statusTextField.wordWrap = false;
statusTextField.width = stage.stageWidth;
statusTextField.text = '';
statusTextField.x = 0;
statusTextField.y = openButton.y + openButton.height + padding;
statusTextField.mouseEnabled = false;
addChild(statusTextField);
}
private function selectFile(evt:MouseEvent) {
var videoFilter:FileFilter = new FileFilter("Videos", "*.flv");
var inputFile:File = File.desktopDirectory;
inputFile.addEventListener(Event.SELECT, fileSelected);
inputFile.browseForOpen('Open', [videoFilter]);
}
private function fileSelected(evt:Event = null) {
inputFile = evt.target as File;
openButton.visible = false;
startVideo();
startFile();
if(DEBUG_THROTTLE_TIME) {
startTimer();
}
}
private function updateStatus(statusText:String) {
statusTextField.text = statusText;
trace(statusText);
}
/*************************
*
* FILE & VIDEO OPERATIONS
*
**************************/
private function startVideo() {
netConnection = new NetConnection();
netConnection.connect(null);
netStream = new NetStream(netConnection);
netStream.client = {};
// put the NetStream class into Data Generation mode
netStream.play(null);
// before appending new bytes, reset the position to the beginning
netStream.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);
updateStatus('Video Stream Started, Waiting for Bytes...');
}
private function startFile() {
totalBytesRead = 0;
readBytes = new ByteArray();
if(DEBUG_UNUSED_BUFFER) {
unusedBuffer = new ByteArray();
}
inputFileStream = new FileStream();
inputFileStream.readAhead = READSIZE;
inputFileStream.addEventListener(ProgressEvent.PROGRESS, fileReadProgress);
inputFileStream.addEventListener(IOErrorEvent.IO_ERROR,ioError);
inputFileStream.openAsync(inputFile, FileMode.READ);
}
private function fileReadProgress(evt:ProgressEvent = null) {
while(inputFileStream.bytesAvailable) {
inputFileStream.readBytes(readBytes, readBytes.length, inputFileStream.bytesAvailable);
if(!DEBUG_THROTTLE_TIME) {
processData();
}
}
}
private function processData(evt:TimerEvent = null) {
var statusString:String;
if(readBytes.length) {
if(DEBUG_APPEND_VIDEO) {
//Here's where things get funky...
netStream.appendBytes(readBytes);
}
totalBytesRead += readBytes.length;
statusString = 'bytes processed now: ' + readBytes.length.toString();
statusString += '\n total bytes processed: ' + totalBytesRead.toString();
statusString += '\n percentage: ' + Math.round((totalBytesRead / inputFile.size) * 100).toString() + '%';
if(DEBUG_UNUSED_BUFFER) {
//Here too....
unusedBuffer.writeBytes(readBytes);
statusString += '\n Unused Buffer size: ' + unusedBuffer.length.toString();
}
updateStatus(statusString);
readBytes.length = 0;
if(totalBytesRead == inputFile.size) {
fileReadComplete();
}
}
}
private function fileReadComplete(evt:Event = null) {
closeAll();
updateStatus('Finished Reading! Yay!');
}
private function ioError(evt:IOErrorEvent) {
closeAll();
updateStatus('IO ERROR!!!!');
}
/*************************
*
* TIMER OPERATIONS
*
**************************/
private function startTimer() {
throttleTimer = new Timer(DEBUG_THROTTLE_TIME);
throttleTimer.addEventListener(TimerEvent.TIMER, processData);
throttleTimer.start();
}
/*************************
*
* CLEANUP
*
**************************/
private function closeAll() {
if(inputFile != null) {
inputFile.cancel();
inputFile = null;
}
if(inputFileStream != null) {
inputFileStream.removeEventListener(ProgressEvent.PROGRESS, fileReadProgress);
inputFileStream.removeEventListener(IOErrorEvent.IO_ERROR,ioError);
inputFileStream.close();
inputFileStream = null;
}
if(readBytes != null) {
readBytes.clear();
readBytes = null;
}
if(unusedBuffer != null) {
unusedBuffer.clear();
unusedBuffer = null;
}
if(throttleTimer != null) {
throttleTimer.removeEventListener(TimerEvent.TIMER, processData);
throttleTimer.stop();
throttleTimer = null;
}
if(netConnection != null) {
netConnection.close();
netConnection = null;
}
if(netStream != null) {
netStream.close();
netStream = null;
}
openButton.visible = true;
}
}
}
更新:
NetStream.seek() 将刷新 appendBytes() 附加的内容......换句话说,appendBytes() 似乎只会继续添加你扔给它的任何数据,这是有道理的。但是-这个问题的核心仍然存在...
理论上,我猜每 10 秒的关键帧调用 seek() 会起作用……这真的有点奇怪,根本不是“seek”通常用于的,而且它需要一大堆手动计算才能使其正常工作,因为在数据生成模式下使用 seek 继续播放将需要调用 appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK),而这反过来又要求对 appendBytes() 的下一次调用需要从 FLV 标签的下一个字节位置开始(希望存在于元数据中)。
这是正确的解决方案吗?Adobe 团队,这是您的想法吗?任何示例代码?!
帮助!:)