//
//  MIRecordPreviewController.mm
//  Montage
//
//  Created by Alex Curylo on Mon Mar 18 2002.
//  Copyright (c) 2002 Arboretum. All rights reserved.
//

#import "MIRecordPreviewController.h"
#import "MISequenceGrabberView.h"

#ifndef TVideoClip_h
#include "TVideoClip.h"
#endif //TVideoClip_h

#import "TArmeDoc.h"
#import "MIMontageDocument.h"
#import "ArmeDataFileRef.h"
#import "MIObject.h"
#import "MovieHelper.h"
#import "MIMainViewController.h"
#import "MITopLevelContainerRep.h"
#import "MIPlayStopPauseController.h"
// DV device control stuff
#include <DVComponentGlue/IsochronousDataHandler.h>
#include <DVComponentGlue/DeviceControl.h>
#include <iokit/IOKitLib.h>
// from ISOCHRONOUS COMPONENTS REFERENCE v1.0 3/16/2001
#define kIDHComponentType 	'ihlr'		/* Component type*/
#define	kIDHSubtypeDV		'dv  '		/* Subtype for DV (over FireWire)*/

pascal OSErr ReceiveSGDataProc(SGChannel channel, Ptr data, long len, long *offset, long chRefCon,
    TimeValue time, short writeType, long refCon)
{
    MISequenceGrabberView *previewer = (MISequenceGrabberView*)refCon;
    if (!previewer) 
        return noErr;
    return [previewer receiveSGData:channel data:data length:len];
}

@implementation MIRecordPreviewController

+ (MIRecordPreviewController*)sharedRecordPreviewController
{
    static MIRecordPreviewController *sharedRecordPreviewController = nil;
    if ( sharedRecordPreviewController == nil )
    {
        sharedRecordPreviewController = [[MIRecordPreviewController alloc] init];
    }
    return sharedRecordPreviewController;
}

- (MIRecordPreviewController*) init
{
    if (( self = [super initWithWindowNibName:@"MIRecordPreview"] ))
    {
		NSPanel* panel = (NSPanel*)[self window];
		[panel setFloatingPanel:YES];
		
        fSequenceGrabber = nil;
        fSGVideoChannel = nil;
        fSGAudioChannel = nil;
        fSGSettings = nil;
        fRecording = NO;
		fMovieFileRef = nil;
        fTimer = nil;
		fDVCamera = nil;
		fControllingCamera = NO;
		
		[self openSeqGrabComponent];
		[self openCameraComponent];
    }
    return self;
}

- (void)awakeFromNib
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil];
}

- (void)applicationWillTerminate:(NSNotification*)theNotification
{
	vernl(6,"[MIRecordPreviewController applicationWillTerminate]");
    [self release];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:nil];

    if (fSGSettings)
        ::DisposeUserData(fSGSettings);
 
	[self closeSeqGrabComponent];
	[self closeCameraComponent];

    [super dealloc];
}

- (void)closeSeqGrabComponent
{
	if (fSequenceGrabber)
		::CloseComponent(fSequenceGrabber);
    fSequenceGrabber = nil;
    fSGVideoChannel = nil;
    fSGAudioChannel = nil;
}

- (void)closeCameraComponent
{
	if (fDVCamera)
	{
		::IDHCloseDevice(fDVCamera);
		::CloseComponent(fDVCamera);
	}
	fDVCamera = nil;
	fControllingCamera = NO;
	[fDeviceControlCheckbox setState:NSOffState];
	[fDeviceControlCheckbox setEnabled:NO];
}

- (void)openCameraComponent
{
	OSErr err = noErr;

    // try to open a camera component
	fDVCamera = ::OpenDefaultComponent(kIDHComponentType, kIDHSubtypeDV);
	if (!fDVCamera)
		return;

	QTAtomContainer deviceList = nil;
	::IDHGetDeviceList( fDVCamera, &deviceList);
	int numberDVDevices = ::QTCountChildrenOfType(deviceList, kParentAtomIsContainer, kIDHDeviceAtomType);
	if (numberDVDevices < 1)
	{
		[self closeCameraComponent];
		return;
	}
	
	// get the atom to first device
	QTAtom deviceAtom = ::QTFindChildByIndex( deviceList, kParentAtomIsContainer, kIDHDeviceAtomType, 1, nil);
	// find the isoch characteristics for this device
	QTAtom isochAtom = ::QTFindChildByIndex( deviceList, deviceAtom, kIDHIsochServiceAtomType, 1, nil);
	// how many configs exist for this device -- we'll just assume at least 1 and first is default
	// int nConfigs = ::QTCountChildrenOfType( deviceList, isochAtom, kIDHIsochModeAtomType);
	// get first configs atom
	QTAtom configAtom = ::QTFindChildByIndex( deviceList, isochAtom, kIDHIsochModeAtomType, 1, nil);
	// find the media type atom
	QTAtom mediaAtom = ::QTFindChildByIndex( deviceList, configAtom, kIDHIsochMediaType, 1, nil);
	::QTLockContainer( deviceList);
	// get the value of the mediaType atom
	OSType mediaType;
	long size = sizeof(mediaType);
	::QTCopyAtomDataToPtr( deviceList, mediaAtom, true, sizeof(mediaType), &mediaType, &size);
	::QTUnlockContainer( deviceList);
	// is this config a video config?
	if( mediaType == kIDHVideoMediaAtomType) // found video device
	{
		fCameraConfig.container = deviceList; // save this config
		fCameraConfig.atom = configAtom;
	}
	::IDHSetDeviceConfiguration( fDVCamera, &fCameraConfig);		
	err = ::IDHOpenDevice( fDVCamera, kIDHOpenForReadTransactions);	
	if (err)
		[self closeCameraComponent];
	else
		[fDeviceControlCheckbox setEnabled:YES];
}

- (void)openSeqGrabComponent
{
    // try to open a capture component
    ComponentResult result = noErr;
    
    // get the capture source component
    ComponentDescription theDesc = { 0 };
    theDesc.componentType = SeqGrabComponentType;
    theDesc.componentSubType = 0;
    theDesc.componentManufacturer = 0;
    theDesc.componentFlags = 0;
    theDesc.componentFlagsMask = 0;
    Component sgCompID = ::FindNextComponent( sgCompID, &theDesc );
    if ( sgCompID != nil )
        fSequenceGrabber = ::OpenComponent( sgCompID );
    if ( fSequenceGrabber == nil )
        return;
    result = ::SGInitialize( fSequenceGrabber );
    if (fSGSettings)
        ::DisposeUserData(fSGSettings);
    result = ::SGGetSettings(fSequenceGrabber, &fSGSettings, 0);
    result = ::SGSetDataRef(fSequenceGrabber, 0, 0, seqGrabDontMakeMovie);
    //result = ::SGSetGWorld( fSequenceGrabber, fBuffer, nil );
	
	[self openChannels];
}

- (void)openChannels
{
	if (!fSequenceGrabber)
		return;
	
	ComponentResult result = noErr;
    OSType channelType = VideoMediaType;
    short channelIndex = 1;
    // get a video channel
    do
    {
        result = ::SGGetIndChannel(fSequenceGrabber, channelIndex, &fSGVideoChannel, &channelType);
		channelIndex++;
    }
    while (!result and (VideoMediaType != channelType));
    if (result)
       fSGVideoChannel = nil;
    if (!fSGVideoChannel)
        result = ::SGNewChannel(fSequenceGrabber, VideoMediaType, &fSGVideoChannel);
	if (fSGVideoChannel)
	{
		result = ::SGSetChannelUsage( fSGVideoChannel, seqGrabRecord 
				/*| seqGrabPreview | seqGrabPlayDuringRecord*/ );
		result = ::SGGetSrcVideoBounds( fSGVideoChannel, &fNativeSize );
		//result = ::SGSetChannelBounds( fSGVideoChannel, &fPreviewBounds);
		result = ::SGSetDataProc(fSequenceGrabber, NewSGDataUPP(ReceiveSGDataProc), (long)fSequenceGrabberView);
	
		// right now these are hardcoded, should eventually be part of project settings
		// http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/SOURCESV/cationfsforvchannelcps.htm
		// http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/SOURCESV/utilityfsforsgchannelcps.htm
		OSType videoType = kMotionJPEGACodecType;
		result = ::SGSetVideoCompressorType(fSGVideoChannel, videoType);
		long projectFPS = 25; // PAL seems like a reasonable default
		result = ::SGSetFrameRate(fSGVideoChannel, projectFPS << 16);
		Rect projectRect = { 0, 0, 240, 320 };
		result = ::SGSetChannelBounds( fSGVideoChannel, &projectRect);
    }

    // get an audio channel
	channelType = SoundMediaType;
    channelIndex = 1;
    do
    {
        result = ::SGGetIndChannel(fSequenceGrabber, channelIndex++, &fSGAudioChannel, &channelType);
    }
    while (!result and (SoundMediaType != channelType));
    if (result)
       fSGAudioChannel = nil;
    if (!fSGAudioChannel)
        result = ::SGNewChannel(fSequenceGrabber, SoundMediaType, &fSGAudioChannel);
    if (fSGAudioChannel)
	{
		result = ::SGSetChannelUsage( fSGAudioChannel, seqGrabRecord
				/*| seqGrabPreview | seqGrabPlayDuringRecord*/ );
		result = ::SGSetDataProc(fSequenceGrabber, NewSGDataUPP(ReceiveSGDataProc), (long)fSequenceGrabberView);
	
		// right now these are hardcoded, should eventually be part of project settings
		// http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/SOURCESV/cationfsforsoundchannelcps.htm
		// note that changes here need synchronizing with MISequenceGrabberView -receiveSGData
		long frequency = 44100; ///$$$QTSGAUDIOMON
		result = ::SGSetSoundInputRate( fSGAudioChannel, frequency << 16 );
		short bits = 16; //$$$QTSGAUDIOMON
		short channels = 2; 
		OSType soundType = k16BitBigEndianFormat; //$$$QTSGAUDIOMON
		result = ::SGSetSoundInputParameters( fSGAudioChannel, bits, channels, soundType );
	}
}

- (void) show
{
	NSPanel* panel = (NSPanel*)[self window];
	[panel setDelegate:self];
	
	[self checkDevices];
	if (!fDVCamera)
		[self openCameraComponent];
	
	[panel orderFront:self];
	
	// The problem was 'window' got unhooked in the nib somehow
	//ZzAssert([panel isVisible]);
}

- (void) checkDevices
{
	if (fSequenceGrabber and fSGVideoChannel)
		return;

	if (!fSequenceGrabber)
		[self openSeqGrabComponent];
	else if (!fSGVideoChannel)
		[self openChannels];

	[self updateDeviceMenu];
	
    if (fSGVideoChannel or fSGAudioChannel)
	{
		[fSequenceGrabberView startPreviewing:fSequenceGrabber video:fSGVideoChannel audio:fSGAudioChannel];
		[self startIdling];
	}
}

- (void) updateDeviceMenu
{
	ComponentResult result;
	
	// get currently selected if any
	// no, we'll coordinate with SG
	//NSString* selected = [fDevicePopup titleOfSelectedItem];
	
	// remove all except 'None' at top
	for( int i = [fDevicePopup numberOfItems]; i > 1; i--)
		[fDevicePopup removeItemAtIndex:1];
	
	if (!fSGVideoChannel)
	{
		[fDevicePopup selectItemAtIndex:0];
		return;
	}
	
	// add devices, select current one if any
	SGDeviceList list = nil;
	result = ::SGGetChannelDeviceList(fSGVideoChannel, 0, &list);
	if (list)
	{
		for( int j = 0; j < (**list).count; j++)
		{
			SGDeviceName sgName = (**list).entry[j];
			char cName[64];
			p2cstrcpy(cName, sgName.name);
			NSString* text = [[NSString alloc] initWithCString:cName];
			[fDevicePopup addItemWithTitle:text];
			NSMenuItem* item = [fDevicePopup itemAtIndex:[fDevicePopup numberOfItems] - 1];
			[item setTarget:self];
			[item setAction:@selector(deviceFromMenu:)];
			//if (selected == text)
			if (j == (**list).selectedIndex)
				[fDevicePopup selectItemAtIndex:[fDevicePopup numberOfItems] - 1];
		}
		
		result = ::SGDisposeDeviceList(fSequenceGrabber, list);
	}
}

- (void)windowWillClose:(NSNotification *)aNotification
{
    // we need to turn off the timer BEFORE the window goes away
    // or the previewer class will NEVER be dealloced!
	if (!fRecording)
	{
		[self stopIdling];
		::SGStop(fSequenceGrabber);
	}
    [fSequenceGrabberView stopPreviewing];
 }

- (void)startIdling
{
   // if (!fPreviewing)
		//return;

    // timer to call ::SGIdle()
    // note that it needs closing down BEFORE window goes away, it retains this view
    float interval = 1./30.;
    fTimer = [NSTimer scheduledTimerWithTimeInterval:interval
            target:self selector:@selector(idleSG:) userInfo:nil repeats:YES];
}

- (void)stopIdling
{
    if (fTimer)
    {
        [fTimer invalidate];
        //[fTimer release]; causes EXC_BAD_ACCESS
        fTimer = nil;
    }
}

- (void)idleSG:(NSTimer*)theTimer
{
    OSErr err = ::SGIdle(fSequenceGrabber);
    if (err)
    {
	::SGStop(fSequenceGrabber);
	::SGStartRecord(fSequenceGrabber);
    }
}

- (IBAction)audioSettings:(id)sender
{
    if (!fSequenceGrabber or !fSGAudioChannel)
    {
        NSBeep();
        return;
    }
		
    ComponentResult err;

    short numPanels = 0;
    Component* panelList = nil;            
    
    [self stopIdling];
    ::SGStop(fSequenceGrabber);

    err = ::SGSettingsDialog(fSequenceGrabber, fSGAudioChannel,
            numPanels, panelList, 
            0L, nil, 0L);
    delete [] panelList;
    
    if (!err)
    {        
        if (fSGSettings)
                {
                ::DisposeUserData(fSGSettings);
                fSGSettings = nil;
                }
        err = ::SGGetSettings(fSequenceGrabber, &fSGSettings, 0);
        
    }

    err = ::SGStartRecord(fSequenceGrabber);
    [self startIdling];
}

- (IBAction)deviceNone:(id)sender
{
	NSBeep();
}

- (IBAction)deviceFromMenu:(id)sender
{
	NSBeep();
}

- (IBAction)toggleCameraControl:(id)sender;
{
	fControllingCamera = NSOnState == [sender state];
	if (sender == fDeviceControlCheckbox)
		[[MIPlayStopPauseController sharedPlayStopPauseController] cameraButtonPressed:sender];
	else
		[fDeviceControlCheckbox setState:(fControllingCamera ? NSOnState : NSOffState)];
}

- (IBAction)videoSettings:(id)sender
{
    if (!fSequenceGrabber or !fSGVideoChannel)
    {
        NSBeep();
        return;
    }
		
    Rect newActiveVideoRect;
    Rect curBounds, curVideoRect, newVideoRect;
    ComponentResult err;

    short numPanels = 0;
    Component* panelList = nil;
	
	// as per 2002-04-16-recording_specs.pdf, 
	// get the video capture source components only
	ComponentDescription theDesc;
	theDesc.componentType = SeqGrabPanelType;
	theDesc.componentSubType = VideoMediaType; // also soun text
	theDesc.componentManufacturer = SeqGrabSourcePanelType; // also cmpr imag
	theDesc.componentFlags = 0;
	theDesc.componentFlagsMask = 0;
	numPanels = ::CountComponents(&theDesc);
	if (!numPanels)
		{
		NSBeep();
		return;
		}
	panelList = new Component[numPanels];
	Component lastID = nil;
	for (int i = 0; i < numPanels ;i++)
		{
		lastID = panelList[i] = ::FindNextComponent( lastID, &theDesc );

		ComponentDescription cd;
		::GetComponentInfo(lastID, &cd, nil, nil, nil);
		}
		
	// Get our current state
    err = ::SGGetChannelBounds (fSGVideoChannel, &curBounds);
    err = ::SGGetVideoRect (fSGVideoChannel, &curVideoRect);

    [self stopIdling];
    ::SGStop(fSequenceGrabber);
    [fSequenceGrabberView stopPreviewing];

    err = ::SGSettingsDialog(fSequenceGrabber, fSGVideoChannel,
            numPanels, panelList, 
            0L, nil, 0L);
    delete [] panelList;
    
    if (!err)
    {
        err = ::SGGetVideoRect (fSGVideoChannel, &newVideoRect);
        err = ::SGGetSrcVideoBounds (fSGVideoChannel, &newActiveVideoRect);
        
        if (fSGSettings)
                {
                ::DisposeUserData(fSGSettings);
                fSGSettings = nil;
                }
        err = ::SGGetSettings(fSequenceGrabber, &fSGSettings, 0);
	}
    
    [fSequenceGrabberView startPreviewing:fSequenceGrabber video:fSGVideoChannel audio:fSGAudioChannel];
    [self startIdling];
}

- (IBAction)record:(id)sender
{
    if (!fSGVideoChannel and !fSGAudioChannel)
    {
        NSBeep();
        return;
    }
    
    if (fRecording)
    {
        [sender setTitle:@"Record"];
        [self stopRecording];
    }
    else
    {
        [sender setTitle:@"Stop"];
        [self startRecording];
    }
}

- (BOOL)isRecording
{
	return fRecording;
}

- (void)stopRecording
{
	if (!fRecording)
		return;
		
    // close up the recording, start preview again
    [self stopIdling];
    ::SGStop(fSequenceGrabber);

    OSErr err = ::SGSetDataRef(fSequenceGrabber, 0, 0, seqGrabDontMakeMovie);
    ::SGSetMaximumRecordTime(fSequenceGrabber, 0);
  
    err = ::SGStartRecord(fSequenceGrabber);
    [self startIdling];

    fRecording = false;

    MIMontageDocument* frontDoc = [[NSDocumentController sharedDocumentController] currentDocument];
	/*
    // all right, now we act as if that file was dropped on the main window
    // from - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
    
    char fullPath[kMaxPathLength];
    FSpPathFromLocation(&fCaptureFile, fullPath);
    NSString* s = [[NSString alloc] initWithCString:fullPath];
    NSArray* files = [NSArray arrayWithObject:s];
    NSPoint position = NSZeroPoint;

    MICreateWSObjsFromFilesCommand *createClipsCommand;
    MIMainViewController* itsController = [NSApp mainViewController];
    MIObject* itsParent = [[[NSApp mainViewController] topLevelContainerView] representedObject];
    */
	
    if (frontDoc)
    {    
		ZzSplitTask* splitTask = NULL;
		ArmeErr err;
		TVideoClip* newClip = nil;
		TArmeDoc* armeDoc = [frontDoc armeDocument];
		newClip = new TVideoClip(fMovieFileRef, armeDoc, &splitTask, &err,  armeDoc->GetZeroPos());
       	if(err.type!=kArmeOK)
        {
            delete newClip;
            fMovieFileRef->Release();
        }
		
		// from - (IBAction)createTextClip:(id)sender
		TArmeWSObjPos			newPos;
		MIObject				*newObj;
		NSPoint visibleRectOrigin = [[[NSApp mainViewController] topLevelContainerView] visibleRect].origin;
	
		armeDoc->LockRead();
		newPos = newClip->GetPos();
		armeDoc->UnlockRead();
	
		newPos.fStartTime = [[[NSApp mainViewController] topLevelContainerView] pixelToArmeTime:(visibleRectOrigin.x+10)];
	
		newClip->SetPos(newPos);
		newClip->_SetLength([[NSApp mainViewController] pixelToArmeTime:200.0]);
	
		newObj = [[MIObject alloc] initWithArmeWSObj:newClip];
		[newObj setMontageDoc:frontDoc];
		[[frontDoc topLevelContainer] addChild:newObj];
	
		[frontDoc lockWrite];
		[newObj setVerticalPos:[[[NSApp mainViewController] topLevelContainerView] pixelToMIVPos:(visibleRectOrigin.y+10)]];
		[frontDoc unlockWrite];
		[newObj createAllRepresentations];
		[newObj setRepresentationsNeedDisplay];
	
		[[MIApplicationController sharedApplicationController] modelChangedForDocument:[[NSDocumentController sharedDocumentController] currentDocument]];

        /*
		createClipsCommand = [[MICreateWSObjsFromFilesCommand alloc] initWithFiles:files controller:itsController];
        [createClipsCommand setBopTask:&bopTask];
        [createClipsCommand setFirstPosition:position];
        [createClipsCommand setParent:itsParent];
        [frontDoc doCmd:createClipsCommand];
		*/
    }
    else
    {
		fMovieFileRef->Release();
        //::DeleteMovieFile(&fCaptureFile);
    }
	fMovieFileRef = nil;
}

- (void)startRecording
{
    if (fRecording or !fSGVideoChannel)
	{
		NSBeep();
        return;
	}
	
    MIMontageDocument* frontDoc = [[NSDocumentController sharedDocumentController] currentDocument];
    if (!frontDoc)
    {
        NSBeep();
        return;
    }
	
	OSStatus err = noErr;
	//OSStatus err = MakeUniqueFSSpec(fCaptureFile, [frontDoc armeDocument]->GetProjectFolderPath(), "Video Capture<%ld>.mov");
	fMovieFileRef = [frontDoc armeDocument]->GetNewVideoDataFileRef();
	char path[kMaxPathLength + 1] = { 0 };
	fMovieFileRef->GetFilePath(path);
	fCaptureFile = CreateFile(path, FOUR_CHAR_CODE('TVOD'), FOUR_CHAR_CODE('MooV'));

    if (!err)
		err = ::CreateMovieFile(&fCaptureFile, 'TVOD', smSystemScript, 
                createMovieFileDeleteCurFile | createMovieFileDontOpenFile
						| createMovieFileDontCreateMovie | createMovieFileDontCreateResFile,
                nil, nil);
    if (err)
    {
        NSBeep();
        return;
    }
	fMovieFileRef->AddRef();
    
    // start recording
    [self stopIdling];
    ::SGStop(fSequenceGrabber);

    err = ::SGSetDataOutput(fSequenceGrabber, &fCaptureFile, seqGrabToDisk);
    
    // we need to specify a max time, otherwise we may run into the eat-all-disk-space bug
    //let's say 10 minutes for now
    unsigned long captureTicks = 10 * 60 * 60;
   ::SGSetMaximumRecordTime(fSequenceGrabber, captureTicks);

    err = ::SGStartRecord(fSequenceGrabber);
    [self startIdling];
    
    fRecording = true;
}

- (BOOL)isDeviceControllable
{
	//if (![[self window] isVisible])
		//return NO;
	//if (!fControllingCamera)
		//return NO;
	return fDVCamera != nil;
}

- (void)deviceControl:(int)opcode1 kind:(int)opcode2
{
	if (!fDVCamera)
		return;

	ComponentInstance controlInst;
	ComponentResult result;
	IDHDeviceStatus devStatus;
	DVCTransactionParams pParams;
	char in[4], out[16];
	int i;
	::IDHGetDeviceControl(fDVCamera, &controlInst);
	::IDHGetDeviceStatus(fDVCamera, &fCameraConfig, &devStatus);
	// fill up the avc frame
	in[0] = 0x00; //kAVCControlCommand;
	in[1] = 0x20;
	in[2] = opcode1;
	in[3] = opcode2;
	// fill up the transaction parameter block
	pParams.commandBufferPtr = in;
	pParams.commandLength = sizeof(in);
	pParams.responseBufferPtr = out;
	pParams.responseBufferSize = sizeof(out);
	pParams.responseHandler = NULL;
	int outSize = sizeof(out);
	do 
	{
		for (i=0; i < outSize; i++)
			out[i] = 0;
		result = ::DeviceControlDoAVCTransaction( controlInst, &pParams);
		if (result == kIOReturnOffline)
		{
			sleep(1);
			continue;
		}
		if(result)
			break;
	}
	while(result != kIOReturnSuccess);

	[self checkDevices];
}

- (void)devicePlay 			{ [self deviceControl:0xC3 kind:0x75]; }
- (void)deviceRewind 		{ [self deviceControl:0xC4 kind:0x65]; }
- (void)deviceRewindPlay 	{ [self deviceControl:0xC3 kind:0x4E]; }
- (void)deviceFastForward 	{ [self deviceControl:0xC4 kind:0x75]; }
- (void)deviceFastForwardPlay { [self deviceControl:0xC3 kind:0x3E]; }
- (void)deviceStop 			{ [self deviceControl:0xC4 kind:0x60]; }
- (void)devicePause 		{ [self deviceControl:0xC3 kind:0x7D]; }
- (void)deviceNextFrame 	{ [self deviceControl:0xC3 kind:0x30]; }
- (void)deviceSlowForward 	{ [self deviceControl:0xC3 kind:0x35]; }
- (void)devicePreviousFrame { [self deviceControl:0xC3 kind:0x40]; }
- (void)deviceSlowRewind 	{ [self deviceControl:0xC3 kind:0x45]; }

@end
