Skip to content
This repository has been archived by the owner on Jul 3, 2019. It is now read-only.

Creation and playback of compressed SWFM files. #2389

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions src/base/dataBuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,21 @@ module Shumway.ArrayUtilities {
return clone;
}

/**
* Removes bytes from start to the current position, and moves the rest of the data
* to the beginning of the buffer. The length will be reduced by the number of
* the removed bytes, and the position will be set to 0.
*/
removeHead(): void {
var position = this._position;
if (position === 0) {
return; // nothing to compact
}
this._u8.set(this._u8.subarray(position, this._length), 0);
this._length -= position;
this._position = 0;
}

/**
* By default, we only have a byte view. All other views are |null|.
*/
Expand Down Expand Up @@ -615,8 +630,19 @@ module Shumway.ArrayUtilities {
}
}

readRawBytes(): Int8Array {
return new Int8Array(this._buffer, 0, this._length);
readRawBytes(length: number): Uint8Array {
if (length < 0) {
release || assert((<any>this).sec);
(<any>this).sec.throwError('RangeError', Errors.ParamRangeError);
}
var position = this._position;
if (position + length > this._length) {
release || assert((<any>this).sec);
(<any>this).sec.throwError('flash.errors.EOFError', Errors.EOFError);
}
var result = new Uint8Array(this._u8.subarray(position, position + length));
this._position = position + 4;
return result;
}

writeUTF(value: string): void {
Expand Down
3 changes: 2 additions & 1 deletion src/flash/display/BitmapData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,8 @@ module Shumway.AVMX.AS.flash.display {
}

setPixels(rect: flash.geom.Rectangle, inputByteArray: flash.utils.ByteArray): void {
this._putPixelData(rect, new Int32Array(inputByteArray.readRawBytes()));
var data = inputByteArray.getBytes();
this._putPixelData(rect, new Int32Array(data.buffer, 0, data.length >> 2));
}

setVector(rect: flash.geom.Rectangle, inputVector: Uint32Vector): void {
Expand Down
26 changes: 22 additions & 4 deletions src/gfx/test/playbackEaselHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module Shumway.GFX.Test {
export class PlaybackEaselHost extends EaselHost {
private _parser: MovieRecordParser;
private _lastTimestamp: number;
private _parsingInProgress: boolean;

public ignoreTimestamps: boolean = false;
public alwaysRenderFrame: boolean = false;
Expand All @@ -38,6 +39,9 @@ module Shumway.GFX.Test {

public constructor(easel: Easel) {
super(easel);
this._parsingInProgress = false;
this._parser = new MovieRecordParser();
this._lastTimestamp = 0;
}

public get cpuTime(): number {
Expand All @@ -49,15 +53,22 @@ module Shumway.GFX.Test {
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
this.playBytes(new Uint8Array(xhr.response));
var data = new Uint8Array(xhr.response);
this.playBytes(data);
this.finish();
}.bind(this);
xhr.send();
}

private playBytes(data: Uint8Array) {
this._parser = new MovieRecordParser(data);
this._lastTimestamp = 0;
this._parseNext();
this._parser.push(data);
if (!this._parsingInProgress) {
this._parseNext();
}
}

private finish() {
this._parser.close();
}

onSendUpdates(updates: DataBuffer, assets: Array<DataBuffer>) {
Expand All @@ -74,7 +85,13 @@ module Shumway.GFX.Test {

private _parseNext() {
var type = this._parser.readNextRecord();
if (type === MovieRecordType.Incomplete) {
this._parsingInProgress = false;
return;
}

if (type !== MovieRecordType.None) {
this._parsingInProgress = true;
var runRecordBound = this._runRecord.bind(this);
var interval = this._parser.currentTimestamp - this._lastTimestamp;
this._lastTimestamp = this._parser.currentTimestamp;
Expand All @@ -87,6 +104,7 @@ module Shumway.GFX.Test {
setTimeout(runRecordBound, interval);
}
} else {
this._parsingInProgress = false;
if (this.onComplete) {
this.onComplete();
}
Expand Down
125 changes: 119 additions & 6 deletions src/gfx/test/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
module Shumway.GFX.Test {
import DataBuffer = Shumway.ArrayUtilities.DataBuffer;
import PlainObjectDataBuffer = Shumway.ArrayUtilities.PlainObjectDataBuffer;
import IDataDecoder = Shumway.ArrayUtilities.IDataDecoder;
import Inflate = Shumway.ArrayUtilities.Inflate;
import LzmaDecoder = Shumway.ArrayUtilities.LzmaDecoder;

enum MovieRecordObjectType {
Undefined = 0,
Expand Down Expand Up @@ -118,6 +121,7 @@ module Shumway.GFX.Test {
}

export const enum MovieRecordType {
Incomplete = -1,
None = 0,
PlayerCommand = 1,
PlayerCommandAsync = 2,
Expand Down Expand Up @@ -150,7 +154,8 @@ module Shumway.GFX.Test {
}

public dump() {
var parser = new MovieRecordParser(this._recording.getBytes());
var parser = new MovieRecordParser();
parser.push(this._recording.getBytes());
parser.dump();
}

Expand Down Expand Up @@ -283,26 +288,132 @@ module Shumway.GFX.Test {
}
}

const enum MovieCompressionType {
None,
ZLib,
Lzma
}

const enum MovieRecordParserState {
Initial,
Parsing,
Ended
}

var MovieHeaderSize = 4;
var MovieRecordHeaderSize = 12;

class IdentityDataTransform implements IDataDecoder {
onData: (data: Uint8Array) => void = null;
onError: (e) => void = null;
push(data: Uint8Array) { this.onData(data); }
close() {}
}

export class MovieRecordParser {
private _buffer: DataBuffer;
private _state: MovieRecordParserState;
private _comressionType: MovieCompressionType;
private _closed: boolean;
private _transform: IDataDecoder;

public currentTimestamp: number;
public currentType: MovieRecordType;
public currentData: DataBuffer;

constructor(data: Uint8Array) {
constructor () {
this._state = MovieRecordParserState.Initial;
this._buffer = new DataBuffer();
this._closed = false;
}

public push(data: Uint8Array): void {
if (this._state === MovieRecordParserState.Initial) {
// SWFM file starts from 4 bytes header: "MSWF", "MSWC", or "MSWZ"
var needToRead = MovieHeaderSize - this._buffer.length;
this._buffer.writeRawBytes(data.subarray(0, needToRead));
if (MovieHeaderSize > this._buffer.length) {
return;
}
this._buffer.position = 0;
var headerBytes = this._buffer.readRawBytes(MovieHeaderSize);
if (headerBytes[0] !== 0x4D || headerBytes[1] !== 0x53 || headerBytes[2] !== 0x57 ||
(headerBytes[3] !== 0x46 && headerBytes[3] !== 0x43 && headerBytes[3] !== 0x5A)) {
console.warn('Invalid SWFM header, stopping parsing');
return;
}
switch (headerBytes[3]) {
case 0x46: // 'F'
this._comressionType = MovieCompressionType.None;
this._transform = new IdentityDataTransform();
break;
case 0x43: // 'C'
this._comressionType = MovieCompressionType.ZLib;
this._transform = Inflate.create(true);
break;
case 0x5A: // 'Z'
this._comressionType = MovieCompressionType.Lzma;
this._transform = new LzmaDecoder(false);
break;
}

this._transform.onData = this._pushTransformed.bind(this);
this._transform.onError = this._errorTransformed.bind(this);
this._state = MovieRecordParserState.Parsing;
this._buffer.clear();
data = data.subarray(needToRead);
}
this._transform.push(data);
}

private _pushTransformed(data: Uint8Array): void {
this._buffer.removeHead();

var savedPosition = this._buffer.position;
this._buffer.position = this._buffer.length;
this._buffer.writeRawBytes(data);
this._buffer.position = 4;
this._buffer.position = savedPosition;
}

public close() {
this._transform.close();
this._closed = true;
}

private _errorTransformed() {
console.warn('Error in SWFM stream');
this.close();
}

public readNextRecord(): MovieRecordType {
if (this._buffer.position >= this._buffer.length) {
if (this._state === MovieRecordParserState.Initial) {
return MovieRecordType.Incomplete;
}
if (this._state === MovieRecordParserState.Ended) {
return MovieRecordType.None;
}

if (this._buffer.position >= this._buffer.length) {
if (this._closed) {
this._state = MovieRecordParserState.Ended;
return MovieRecordType.None;
}
return MovieRecordType.Incomplete;
}

if (this._buffer.position + MovieRecordHeaderSize > this._buffer.length) {
return MovieRecordType.Incomplete;
}

var timestamp: number = this._buffer.readInt();
var type: MovieRecordType = this._buffer.readInt();
var length: number = this._buffer.readInt();
var length: number = this._buffer.readInt() >>> 0;

if (this._buffer.position + MovieRecordHeaderSize + length > this._buffer.length) {
this._buffer.position -= MovieRecordHeaderSize;
return MovieRecordType.Incomplete;
}

var data: DataBuffer = null;

if (length > 0) {
Expand Down Expand Up @@ -350,7 +461,7 @@ module Shumway.GFX.Test {

public dump() {
var type: MovieRecordType;
while ((type = this.readNextRecord())) {
while ((type = this.readNextRecord()) !== MovieRecordType.None) {
console.log('record ' + type + ' @' + this.currentTimestamp);
switch (type) {
case MovieRecordType.PlayerCommand:
Expand All @@ -366,6 +477,8 @@ module Shumway.GFX.Test {
case MovieRecordType.Image:
console.log(this.parseImage());
break;
case MovieRecordType.Incomplete:
return;
}
}
}
Expand Down
Loading