CocoaSplit/CSFFMpegCapturePlugin/CSFFMpegCapturePlugin/CSFFMpegCapture.m
Zakk 970bea7db2 Drop frames if the compressor queue gets too deep (hardcoded to 10 frames, may make it dynamic later?)
Re-enable proper restoration of audio input source settings (oops)
Clear layout source list if a layout recording is stopped and there is no longer a recorder attached to the layout
Fix OAuth request percent encoding for auth codes
Don't change ffmpeg input source size on input file swap
Uninit audio unit with setting input stream format
Start of virtual camera plugin (soooooon). Disabled for now
Update Youtube output plugin
2020-05-15 00:11:52 -04:00

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 44.1k for now.
_audioFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100.0f channels:2];
self.player = [[CSFFMpegPlayer alloc] init];
self.player.audioFormat = _audioFormat;
__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];
}];
}
if (item)
{
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];
} else {
self.currentTimeString = [self timeToString:0.0f];
}
});
}
-(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)
{
if (NSEqualSizes(_lastSize, NSZeroSize))
{
_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:_audioFormat];
if (self.pcmPlayer && self.player)
{
self.player.audioFormat = _audioFormat;
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