Skip to content

Commit

Permalink
feat: Media Upload API, File Transfer Manager
Browse files Browse the repository at this point in the history
  • Loading branch information
Balte de Wit authored and Alex Van Camp committed Oct 4, 2018
1 parent b9980e3 commit c240477
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 21 deletions.
22 changes: 22 additions & 0 deletions src/atem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AtemSocket } from './lib/atemSocket'
import { IPCMessageType, MacroAction } from './enums'
import AbstractCommand from './commands/AbstractCommand'
import * as Commands from './commands'
import * as DataTransferCommands from './commands/DataTransfer'
import { MediaPlayer } from './state/media'
import {
DipTransitionSettings,
Expand All @@ -18,6 +19,8 @@ import {
import * as USK from './state/video/upstreamKeyers'
import { InputChannel } from './state/input'
import { DownstreamKeyerGeneral, DownstreamKeyerMask } from './state/video/downstreamKeyers'
import * as DT from './dataTransfer'
import { Util } from './lib/atemUtil'

export interface AtemOptions {
address?: string,
Expand All @@ -36,6 +39,7 @@ export class Atem extends EventEmitter {
event: EventEmitter
state: AtemState
private socket: AtemSocket
private dataTransferManager: DT.DataTransferManager
private _log: (...args: any[]) => void
private _sentQueue: {[packetId: string]: AbstractCommand } = {}

Expand All @@ -55,6 +59,9 @@ export class Atem extends EventEmitter {
address: (options || {}).address,
port: (options || {}).port
})
this.dataTransferManager = new DT.DataTransferManager(
(command: AbstractCommand) => this.sendCommand(command)
)
this.socket.on('receivedStateChange', (command: AbstractCommand) => this._mutateState(command))
this.socket.on(IPCMessageType.CommandAcknowledged, ({trackingId}: {trackingId: number}) => this._resolveCommand(trackingId))
this.socket.on('error', (e) => this.emit('error', e))
Expand Down Expand Up @@ -332,11 +339,26 @@ export class Atem extends EventEmitter {
return this.sendCommand(command)
}

uploadMedia (props: DT.DataTransferProperties) {
const resolution = Util.getResolution(this.state.settings.videoMode)
return this.dataTransferManager.newTransfer(
props.type,
props.pool,
{ name: props.name, description: props.description },
Util.convertPNGToYUV422(resolution[0], resolution[1], props.data)
)
}

private _mutateState (command: AbstractCommand) {
if (typeof command.applyToState === 'function') {
command.applyToState(this.state)
this.emit('stateChanged', this.state, command)
}
for (const commandName in DataTransferCommands) {
if (command.constructor.name === commandName) {
this.dataTransferManager.processAtemCommand(command)
}
}
}

private _resolveCommand (trackingId: number) {
Expand Down
52 changes: 31 additions & 21 deletions src/dataTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@ import AbstractCommand from './commands/AbstractCommand'

const TIMEOUT = 5000

export interface DataTransferProperties {
type: Enums.StoragePool
pool: number,
name: string
description: string
data: Buffer
}

export class DataTransferManager {
private transfers: Array<DataTransfer>
private dataTransferQueue: Array<AbstractCommand>
private sendCommand: (command: AbstractCommand) => void
private locks = [ false, false, false ]
transfers: Array<DataTransfer> = []
locks = [ false, false, false ]
private _dataTransferQueue: Array<AbstractCommand> = []
private _sendCommand: (command: AbstractCommand) => void
private lastTransferIndex = 0

constructor () {
constructor (sendCommand: (command: AbstractCommand) => void) {
this._sendCommand = sendCommand
setInterval(() => {
if (this.dataTransferQueue.length > 0) {
this.sendCommand(this.dataTransferQueue.shift()!)
if (this._dataTransferQueue.length > 0) {
this._sendCommand(this._dataTransferQueue.shift()!)
}
const now = Date.now()
for (const transfer of this.transfers) {
Expand All @@ -32,7 +41,7 @@ export class DataTransferManager {
index: command.properties.index,
locked: false
})
this.sendCommand(command)
this._sendCommand(command)
} else {
this._proceedNext()
}
Expand All @@ -52,20 +61,25 @@ export class DataTransferManager {
}

newTransfer (type: Enums.StoragePool, pool: number, description: { name: string, description: string }, data: Buffer) {
this.transfers.push(new DataTransfer(
const transfer = new DataTransfer(
this.lastTransferIndex++,
pool,
type,
description,
data,
this._queueCommand
))
(command: AbstractCommand) => this._dataTransferQueue.push(command)
)
const ps = new Promise((resolve, reject) => {
transfer.finish = resolve
transfer.fail = reject
})
this.transfers.push(transfer)

if (this.locks[type] === false) {
this._startNext()
}

return this.lastTransferIndex
return ps
}

cancelTransfer (transferId: number) {
Expand All @@ -92,24 +106,20 @@ export class DataTransferManager {
size: tranfer.data.length,
mode: tranfer.type === Enums.StoragePool.Sounds ? Enums.TransferMode.WriteAudio : Enums.TransferMode.Write
})
this.sendCommand(command)
this._sendCommand(command)
tranfer.transferring = true
tranfer.lastSent = Date.now()
}
}
}

private _queueCommand (command: AbstractCommand) {
this.dataTransferQueue.push(command)
}

private _releaseLock (type: number) {
const command = new Commands.LockStateCommand()
command.updateProps({
index: type,
locked: false
})
this.sendCommand(command)
this._sendCommand(command)
}

private _getLock (type: number) {
Expand All @@ -118,7 +128,7 @@ export class DataTransferManager {
index: type,
locked: true
})
this.sendCommand(command)
this._sendCommand(command)
}

private _failTransfer (transferId: number) {
Expand Down Expand Up @@ -191,15 +201,15 @@ export class DataTransfer {

if (!this._sentDesc) {
const command = new Commands.DataTransferFileDescriptionCommand()
command.updateProps({...this.description, hash: this._hash})
command.updateProps({...this.description, fileHash: this._hash})
this._queueCommand(command)
}

for (let i = 0; i < chunkCount; i++) {
const command = new Commands.DataTransferDataCommand()
command.updateProps({
transferId: this.index,
data: this.data.slice(this._sent, this._sent + chunkSize)
body: this.data.slice(this._sent, this._sent + chunkSize)
})
this._queueCommand(command)
this._sent += chunkSize
Expand Down
52 changes: 52 additions & 0 deletions src/lib/atemUtil.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IPCMessageType } from '../enums'
import * as pRetry from 'p-retry'
import { Enums } from '..'

export namespace Util {
export function stringToBytes (str: string): Array<number> {
Expand Down Expand Up @@ -90,4 +91,55 @@ export namespace Util {
0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00
])

/**
* @todo: BALTE - 2018-5-14:
* Create util functions that handle proper colour spaces.
*/
export function convertPNGToYUV422 (width: number, height: number, data: Buffer) {
const buffer = new Buffer(width * height * 4)
let i = 0
while (i < width * height * 4) {
const r1 = data[i + 0]
const g1 = data[i + 1]
const b1 = data[i + 2]
const a1 = data[i + 3] * 3.7
const r2 = data[i + 4]
const g2 = data[i + 5]
const b2 = data[i + 6]
const a2 = data[i + 7] * 3.7
const y1 = (((66 * r1 + 129 * g1 + 25 * b1 + 128) >> 8) + 16) * 4 - 1
const u1 = (((-38 * r1 - 74 * g1 + 112 * b1 + 128) >> 8) + 128) * 4 - 1
const y2 = (((66 * r2 + 129 * g2 + 25 * b2 + 128) >> 8) + 16) * 4 - 1
const v2 = (((112 * r2 - 94 * g2 - 18 * b2 + 128) >> 8) + 128) * 4 - 1
buffer[i + 0] = a1 >> 4
buffer[i + 1] = ((a1 & 0x0f) << 4) | (u1 >> 6)
buffer[i + 2] = ((u1 & 0x3f) << 2) | (y1 >> 8)
buffer[i + 3] = y1 & 0xf
buffer[i + 4] = a2 >> 4
buffer[i + 5] = ((a2 & 0x0f) << 4) | (v2 >> 6)
buffer[i + 6] = ((v2 & 0x3f) << 2) | (y2 >> 8)
buffer[i + 7] = y2 & 0xff
i = i + 8
}
return buffer
}

export function getResolution (videoMode: Enums.VideoMode) {
const PAL = [720, 576]
const NTSC = [640, 480]
const HD = [1280, 720]
const FHD = [1920, 1080]
const UHD = [3840, 2160]

const enumToResolution = [
NTSC, PAL, NTSC, PAL,
HD, HD,
FHD, FHD, FHD, FHD, FHD, FHD, FHD, FHD,
UHD, UHD, UHD, UHD,
UHD, UHD
]

return enumToResolution[videoMode]
}
}
3 changes: 3 additions & 0 deletions src/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { MacroPlayerState } from './macroPlayer'

export class AtemState {
info = new DeviceInfo()
settings = {
videoMode: 0
}
video: AtemVideoState = new AtemVideoState()
channels: Array<{
name: string
Expand Down

0 comments on commit c240477

Please sign in to comment.