mirror of
https://github.com/zakk4223/CocoaSplit.git
synced 2026-05-15 22:00:34 -06:00
574 lines
12 KiB
Objective-C
574 lines
12 KiB
Objective-C
//
|
|
// CSFFMpegCapture.m
|
|
// CSFFMpegCapturePlugin
|
|
//
|
|
// Created by Zakk on 6/11/16.
|
|
//
|
|
|
|
#import "CSFFMpegCapture.h"
|
|
|
|
@interface InputSourceHack
|
|
@property (assign) bool active;
|
|
@end
|
|
|
|
|
|
@implementation CSFFMpegCapture
|
|
|
|
@synthesize currentMovieTime = _currentMovieTime;
|
|
@synthesize repeat = _repeat;
|
|
|
|
-(instancetype) init
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
|
|
_lastSize = NSZeroSize;
|
|
|
|
self.needsSourceSelection = NO;
|
|
self.playWhenLive = YES;
|
|
self.updateMovieTime = YES;
|
|
self.deactivateWhenDone = NO;
|
|
self.activeVideoDevice = [[CSAbstractCaptureDevice alloc] init];
|
|
_firstFrame = YES;
|
|
_repeat = kCSFFMovieRepeatNone;
|
|
self.uuid = [[NSUUID UUID] UUIDString];
|
|
self.allowDedup = NO; //Seeking makes this impossible
|
|
|
|
|
|
}
|
|
return self;
|
|
}
|
|
|
|
+(NSSet *)mediaUTIs
|
|
{
|
|
return [NSSet setWithArray:@[@"public.movie"]];
|
|
}
|
|
|
|
|
|
+(NSObject<CSCaptureSourceProtocol> *)createSourceFromPasteboardItem:(NSPasteboardItem *)item
|
|
{
|
|
|
|
CSFFMpegCapture *ret = nil;
|
|
|
|
NSString *imagePath = [item stringForType:@"public.file-url"];
|
|
if (imagePath)
|
|
{
|
|
NSURL *fileURL = [NSURL URLWithString:imagePath];
|
|
NSString *realPath = [fileURL path];
|
|
ret = [[CSFFMpegCapture alloc] init];
|
|
[ret queuePath:realPath];
|
|
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
-(void)setRepeat:(ff_movie_repeat)repeat
|
|
{
|
|
_repeat = repeat;
|
|
|
|
if (self.player)
|
|
{
|
|
self.player.repeat = repeat;
|
|
}
|
|
}
|
|
|
|
-(ff_movie_repeat)repeat
|
|
{
|
|
if (self.player)
|
|
{
|
|
_repeat = self.player.repeat;
|
|
|
|
}
|
|
|
|
return _repeat;
|
|
}
|
|
|
|
|
|
|
|
|
|
-(void)setupPlayer
|
|
{
|
|
|
|
avformat_network_init();
|
|
|
|
|
|
|
|
//Inputs resample to floating point non-interleaved 48k for now.
|
|
|
|
_asbd.mSampleRate = 48000;
|
|
_asbd.mFormatID = kAudioFormatLinearPCM;
|
|
_asbd.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved;
|
|
_asbd.mChannelsPerFrame = 2;
|
|
_asbd.mBitsPerChannel = 32;
|
|
_asbd.mBytesPerFrame = 4;
|
|
_asbd.mBytesPerPacket = 4;
|
|
_asbd.mFramesPerPacket = 1;
|
|
|
|
self.player = [[CSFFMpegPlayer alloc] init];
|
|
|
|
self.player.asbd = &_asbd;
|
|
|
|
__weak __typeof__(self) weakSelf = self;
|
|
|
|
_player.itemStarted = ^(CSFFMpegInput *item) { [weakSelf itemStarted:item]; };
|
|
_player.queueStateChanged = ^() { [weakSelf queueChanged]; };
|
|
_player.repeat = _repeat;
|
|
|
|
|
|
}
|
|
|
|
|
|
-(void)saveWithCoder:(NSCoder *)aCoder
|
|
{
|
|
NSMutableArray *queuePaths = [[NSMutableArray alloc] init];
|
|
|
|
for (CSFFMpegInput *inp in self.player.inputQueue)
|
|
{
|
|
[queuePaths addObject:inp.mediaPath];
|
|
}
|
|
|
|
[aCoder encodeObject:queuePaths forKey:@"queuePaths"];
|
|
|
|
CSFFMpegInput *nowPlaying = self.player.currentlyPlaying;
|
|
|
|
NSString *nPath = nil;
|
|
|
|
if (nowPlaying)
|
|
{
|
|
nPath = nowPlaying.mediaPath;
|
|
}
|
|
|
|
[aCoder encodeObject:nPath forKey:@"nowPlayingPath"];
|
|
[aCoder encodeBool:self.playWhenLive forKey:@"playWhenLive"];
|
|
[aCoder encodeBool:self.useCurrentPosition forKey:@"useCurrentPosition"];
|
|
[aCoder encodeDouble:_currentMovieTime forKey:@"savedTime"];
|
|
[aCoder encodeInt:self.repeat forKey:@"repeat"];
|
|
[aCoder encodeObject:self.uuid forKey:@"uuid"];
|
|
[aCoder encodeBool:self.deactivateWhenDone forKey:@"deactivateWhenDone"];
|
|
}
|
|
|
|
|
|
-(void)restoreWithCoder:(NSCoder *)aDecoder
|
|
{
|
|
|
|
|
|
CSFFMpegInput *nowPlayingInput = nil;
|
|
NSString *nowPlayingPath = [aDecoder decodeObjectForKey:@"nowPlayingPath"];
|
|
|
|
self.uuid = [aDecoder decodeObjectForKey:@"uuid"];
|
|
|
|
if (!self.uuid)
|
|
{
|
|
self.uuid = [[NSUUID UUID] UUIDString];
|
|
}
|
|
NSArray *paths = [aDecoder decodeObjectForKey:@"queuePaths"];
|
|
|
|
for (NSString *mPath in paths)
|
|
{
|
|
if (!self.player)
|
|
{
|
|
[self setupPlayer];
|
|
}
|
|
CSFFMpegInput *newInput = [[CSFFMpegInput alloc] initWithMediaPath:mPath];
|
|
|
|
[self.player enqueueItem:newInput];
|
|
if (nowPlayingPath && [newInput.mediaPath isEqualToString:nowPlayingPath])
|
|
{
|
|
nowPlayingInput = newInput;
|
|
}
|
|
}
|
|
|
|
if (nowPlayingInput)
|
|
{
|
|
|
|
self.player.currentlyPlaying = nowPlayingInput;
|
|
self.captureName = nowPlayingInput.shortName;
|
|
|
|
} else {
|
|
CSFFMpegInput *firstItem = self.player.inputQueue.firstObject;
|
|
|
|
self.captureName = firstItem.shortName;
|
|
}
|
|
|
|
[self registerPCMOutput:self.captureName];
|
|
_savedTime = [aDecoder decodeDoubleForKey:@"savedTime"];
|
|
self.useCurrentPosition = [aDecoder decodeBoolForKey:@"useCurrentPosition"];
|
|
|
|
self.playWhenLive = [aDecoder decodeBoolForKey:@"playWhenLive"];
|
|
self.repeat = [aDecoder decodeIntForKey:@"repeat"];
|
|
if ([aDecoder containsValueForKey:@"deactivateWhenDone"])
|
|
{
|
|
self.deactivateWhenDone = [aDecoder decodeBoolForKey:@"deactivateWhenDone"];
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
+(NSString *)label
|
|
{
|
|
return @"Movie";
|
|
}
|
|
|
|
-(void) generateUniqueID
|
|
{
|
|
NSMutableString *uID = [NSMutableString string];
|
|
|
|
|
|
for(CSFFMpegInput *item in self.player.inputQueue)
|
|
{
|
|
NSString *itemStr = item.mediaPath;
|
|
[uID appendString:itemStr];
|
|
}
|
|
|
|
/*
|
|
if (_pcmPlayer)
|
|
{
|
|
_pcmPlayer.nodeUID = uID;
|
|
}*/
|
|
|
|
|
|
self.activeVideoDevice.uniqueID = uID;
|
|
}
|
|
|
|
|
|
-(NSImage *)libraryImage
|
|
{
|
|
return [NSImage imageNamed:@"NSMediaBrowserMediaTypeMoviesTemplate32"];
|
|
}
|
|
|
|
|
|
-(double)currentMovieTime
|
|
{
|
|
return _currentMovieTime;
|
|
}
|
|
|
|
-(void)setCurrentMovieTime:(double)currentMovieTime
|
|
{
|
|
if (self.player)
|
|
{
|
|
|
|
[self.player seek:currentMovieTime];
|
|
self.currentTimeString = [self timeToString:self.player.lastVideoTime];
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-(void)queueChanged
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self generateUniqueID];
|
|
});
|
|
}
|
|
|
|
|
|
-(void)itemStarted:(CSFFMpegInput *)item
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
if (!item && self.deactivateWhenDone)
|
|
{
|
|
[self updateInputWithBlock:^(id input) {
|
|
InputSourceHack *inp = input;
|
|
[CATransaction begin];
|
|
[CATransaction setAnimationDuration:0.25f];
|
|
inp.active = NO;
|
|
[CATransaction commit];
|
|
}];
|
|
}
|
|
self.durationString = [self timeToString:item.duration];
|
|
self.currentMovieDuration = item.duration;
|
|
[self generateUniqueID];
|
|
self.captureName = item.shortName;
|
|
if (self.pcmPlayer)
|
|
{
|
|
self.pcmPlayer.name = item.shortName;
|
|
}
|
|
[self changeAttachedAudioInputName:self.uuid withName:item.shortName];
|
|
});
|
|
|
|
}
|
|
|
|
|
|
-(void)queuePath:(NSString *)path
|
|
{
|
|
if (!self.player)
|
|
{
|
|
[self setupPlayer];
|
|
}
|
|
|
|
|
|
if (!self.player.pcmPlayer && self.pcmPlayer)
|
|
{
|
|
self.player.pcmPlayer = self.pcmPlayer;
|
|
}
|
|
|
|
CSFFMpegInput *newItem = [[CSFFMpegInput alloc] initWithMediaPath:path];
|
|
|
|
[self.player enqueueItem:newItem ];
|
|
CSFFMpegInput *firstItem = self.player.inputQueue.firstObject;
|
|
|
|
if (!self.captureName)
|
|
{
|
|
if (firstItem)
|
|
{
|
|
self.captureName = firstItem.shortName;
|
|
if (self.pcmPlayer)
|
|
{
|
|
self.pcmPlayer.name = firstItem.shortName;
|
|
}
|
|
}
|
|
}
|
|
|
|
[self registerPCMOutput:firstItem.shortName];
|
|
|
|
|
|
[self generateUniqueID];
|
|
}
|
|
|
|
-(void)pause
|
|
{
|
|
if (self.player)
|
|
{
|
|
[self.player pause];
|
|
}
|
|
}
|
|
|
|
|
|
-(void)play
|
|
{
|
|
if (self.player)
|
|
{
|
|
[self.player play];
|
|
}
|
|
}
|
|
|
|
|
|
-(void)mute
|
|
{
|
|
if (self.player)
|
|
{
|
|
self.player.muted = !self.player.muted;
|
|
}
|
|
}
|
|
|
|
-(void)next
|
|
{
|
|
if (self.player)
|
|
{
|
|
[self.player next];
|
|
}
|
|
}
|
|
|
|
-(void)back
|
|
{
|
|
if (self.player)
|
|
{
|
|
[self.player back];
|
|
}
|
|
}
|
|
|
|
|
|
-(CALayer *)createNewLayer
|
|
{
|
|
return [CALayer layer];
|
|
/*
|
|
CSIOSurfaceLayer *newLayer = [CSIOSurfaceLayer layer];
|
|
|
|
|
|
return newLayer;
|
|
*/
|
|
}
|
|
|
|
|
|
-(NSString *) timeToString:(double)convertTime
|
|
{
|
|
|
|
UInt64 minutes = convertTime/60;
|
|
UInt64 seconds = (int)convertTime % 60;
|
|
return [NSString stringWithFormat:@"%02lld:%02lld", minutes, seconds];
|
|
}
|
|
|
|
|
|
|
|
-(NSSize)captureSize
|
|
{
|
|
return _lastSize;
|
|
}
|
|
|
|
-(void)setIsVisible:(bool)isVisible
|
|
{
|
|
[super setIsVisible:isVisible];
|
|
if (isVisible && self.playWhenLive && !self.player.playing)
|
|
{
|
|
|
|
[self.player play];
|
|
if (self.useCurrentPosition)
|
|
{
|
|
[self.player seek:_savedTime];
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void)frameTick
|
|
{
|
|
if (!self.player)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
CFTimeInterval cTime = CACurrentMediaTime();
|
|
CVPixelBufferRef use_buf = [self.player frameForMediaTime:cTime];
|
|
|
|
if (use_buf)
|
|
{
|
|
|
|
_lastSize = NSMakeSize(CVPixelBufferGetWidth(use_buf), CVPixelBufferGetHeight(use_buf));
|
|
|
|
if ((cTime - _lastTimeUpdate > 0.5) && self.updateMovieTime)
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
self.currentTimeString = [self timeToString:self.player.lastVideoTime];
|
|
[self willChangeValueForKey:@"currentMovieTime"];
|
|
self->_currentMovieTime = self.player.lastVideoTime;;
|
|
|
|
[self didChangeValueForKey:@"currentMovieTime"];
|
|
});
|
|
}
|
|
[self updateLayersWithFramedataBlock:^(CALayer *layer) {
|
|
layer.contents = (__bridge id _Nullable)(use_buf);
|
|
} withPreuseBlock:^{
|
|
CVPixelBufferRetain(use_buf);
|
|
} withPostuseBlock:^{
|
|
CVPixelBufferRelease(use_buf);
|
|
}];
|
|
|
|
if (_firstFrame)
|
|
{
|
|
_firstFrame = NO;
|
|
//[self.player pause];
|
|
}
|
|
CVPixelBufferRelease(use_buf);
|
|
|
|
} else if (_firstFrame) {
|
|
use_buf = [self.player firstFrame];
|
|
if (use_buf)
|
|
{
|
|
_lastSize = NSMakeSize(CVPixelBufferGetWidth(use_buf), CVPixelBufferGetHeight(use_buf));
|
|
[self updateLayersWithFramedataBlock:^(CALayer *layer) {
|
|
layer.contents = (__bridge id _Nullable)(use_buf);
|
|
} withPreuseBlock:^{
|
|
CVPixelBufferRetain(use_buf);
|
|
} withPostuseBlock:^{
|
|
CVPixelBufferRelease(use_buf);
|
|
}];
|
|
CVPixelBufferRelease(use_buf);
|
|
_firstFrame = NO;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
-(void)setIsLive:(bool)isLive
|
|
{
|
|
|
|
bool oldLive = super.isLive;
|
|
super.isLive = isLive;
|
|
|
|
if (isLive == oldLive)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (isLive)
|
|
{
|
|
if (self.player)
|
|
{
|
|
[self registerPCMOutput:1024 audioFormat:&_asbd];
|
|
}
|
|
|
|
} else {
|
|
[self deregisterPCMOutput];
|
|
}
|
|
}
|
|
*/
|
|
|
|
-(void)registerPCMOutput:(NSString *)withName
|
|
{
|
|
|
|
if (!self.pcmPlayer)
|
|
{
|
|
self.pcmPlayer = [self createAttachedAudioInputForUUID:self.uuid withName:withName withFormat:&_asbd];
|
|
|
|
if (self.pcmPlayer && self.player)
|
|
{
|
|
self.player.asbd = &_asbd;
|
|
self.player.pcmPlayer = self.pcmPlayer;
|
|
if (self.player.currentlyPlaying)
|
|
{
|
|
self.pcmPlayer.name = self.player.currentlyPlaying.shortName;
|
|
} else {
|
|
CSFFMpegInput *firstItem = self.player.inputQueue.firstObject;
|
|
|
|
self.pcmPlayer.name = firstItem.shortName;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-(float)duration
|
|
{
|
|
float startpos = 0;
|
|
if (self.useCurrentPosition)
|
|
{
|
|
startpos = _savedTime;
|
|
}
|
|
float totalDuration = 0;
|
|
for (CSFFMpegInput *item in self.player.inputQueue)
|
|
{
|
|
totalDuration += item.duration;
|
|
}
|
|
return totalDuration - startpos;
|
|
}
|
|
|
|
|
|
-(void)deregisterPCMOutput
|
|
{
|
|
|
|
|
|
if (self.pcmPlayer)
|
|
{
|
|
[[CSPluginServices sharedPluginServices] removePCMInput:self.pcmPlayer];
|
|
}
|
|
|
|
self.pcmPlayer = nil;
|
|
if (self.player)
|
|
{
|
|
self.player.pcmPlayer = nil;
|
|
}
|
|
|
|
}
|
|
|
|
-(void)dealloc
|
|
{
|
|
if (self.pcmPlayer)
|
|
{
|
|
[self deregisterPCMOutput];
|
|
}
|
|
[self.player stop];
|
|
|
|
self.player = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|