summaryrefslogtreecommitdiffstats
path: root/macosx/HBQTKitPlayer.m
diff options
context:
space:
mode:
Diffstat (limited to 'macosx/HBQTKitPlayer.m')
-rw-r--r--macosx/HBQTKitPlayer.m404
1 files changed, 404 insertions, 0 deletions
diff --git a/macosx/HBQTKitPlayer.m b/macosx/HBQTKitPlayer.m
new file mode 100644
index 000000000..012edc01b
--- /dev/null
+++ b/macosx/HBQTKitPlayer.m
@@ -0,0 +1,404 @@
+/* HBQTKitPlayer.m $
+
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr/>.
+ It may be used under the terms of the GNU General Public License. */
+
+#import "HBQTKitPlayer.h"
+#import <QTKit/QTKit.h>
+
+@import HandBrakeKit;
+
+@interface QTTrack (HBAdditions)
+- (id)isoLanguageCodeAsString;
+@end
+
+typedef void (^HBPeriodicObverser)(NSTimeInterval time);
+typedef void (^HBRateObverser)(void);
+typedef void (^HBPlayableObverser)(void);
+
+@interface HBQTKitPlayerPeriodicObserver : NSObject
+
+@property (nonatomic) HBPeriodicObverser block;
+
+- (void)postNotification:(NSTimeInterval)time;
+
+@end
+
+@implementation HBQTKitPlayerPeriodicObserver
+
+- (void)postNotification:(NSTimeInterval)time
+{
+ self.block(time);
+}
+
+@end
+
+@interface HBQTKitPlayerRateObserver : NSObject
+
+@property (nonatomic) HBRateObverser block;
+
+- (void)postNotification;
+
+@end
+
+@implementation HBQTKitPlayerRateObserver
+
+- (void)postNotification;
+{
+ self.block();
+}
+
+@end
+
+@interface HBQTKitPlayer ()
+
+@property (nonatomic, strong) QTMovie *movie;
+@property (nonatomic, strong) NSTimer *timer;
+
+@property (nonatomic, readwrite, getter=isPlayable) BOOL playable;
+
+@property (nonatomic, strong) NSMutableSet<HBQTKitPlayerPeriodicObserver *> *periodicObservers;
+@property (nonatomic, strong) NSMutableSet<HBQTKitPlayerRateObserver *> *rateObservers;
+
+@property (nonatomic, strong) NSMutableSet<HBPlayableObverser> *playableObservers;
+
+@end
+
+@implementation HBQTKitPlayer
+
+- (instancetype)initWithURL:(NSURL *)url
+{
+ self = [super init];
+
+ if (self)
+ {
+ NSError *outError;
+ NSDictionary *attributes = @{ QTMovieURLAttribute: url,
+ QTMovieAskUnresolvedDataRefsAttribute: @NO,
+ QTMovieOpenForPlaybackAttribute: @YES,
+ QTMovieIsSteppableAttribute: @YES,
+ QTMovieOpenAsyncRequiredAttribute: @YES,
+ QTMovieApertureModeAttribute: QTMovieApertureModeClean };
+
+ _movie = [[QTMovie alloc] initWithAttributes:attributes error:&outError];
+
+ if (!_movie)
+ {
+ return nil;
+ }
+
+ _movie.delegate = self;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(_movieRateDidChange:)
+ name:QTMovieRateDidChangeNotification
+ object:_movie];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(_loadStateChanged:)
+ name:QTMovieLoadStateDidChangeNotification
+ object:_movie];
+
+ _layer = [QTMovieLayer layerWithMovie:_movie];
+
+ if (!_layer)
+ {
+ return nil;
+ }
+
+ _periodicObservers = [NSMutableSet set];
+ _rateObservers = [NSMutableSet set];
+ _playableObservers = [NSMutableSet set];
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [self _stopMovieTimer];
+}
+
+- (void)setPlayable:(BOOL)playable
+{
+ _playable = playable;
+
+ for (HBPlayableObverser block in self.playableObservers)
+ {
+ block();
+ }
+ [self.playableObservers removeAllObjects];
+}
+
+- (void)_loadStateChanged:(NSNotification *)notification
+{
+ int loadState = [[self.movie attributeForKey:QTMovieLoadStateAttribute] intValue];
+
+ if (loadState >= QTMovieLoadStateLoaded)
+ {
+ [self _enableSubtitles];
+ self.playable = YES;
+ }
+}
+
+- (void)_movieRateDidChange:(NSNotification *)notification
+{
+ for (HBQTKitPlayerRateObserver *observer in self.rateObservers)
+ {
+ [observer postNotification];
+ }
+}
+
+- (void)_enableSubtitles
+{
+ // Get and enable subtitles
+ NSArray<QTTrack *> *subtitlesArray = [self.movie tracksOfMediaType:QTMediaTypeSubtitle];
+ if (subtitlesArray.count)
+ {
+ // enable the first tx3g subtitle track
+ [subtitlesArray.firstObject setEnabled:YES];
+ }
+ else
+ {
+ // Perian subtitles
+ subtitlesArray = [self.movie tracksOfMediaType:QTMediaTypeVideo];
+ if (subtitlesArray.count >= 2)
+ {
+ // track 0 should be video, other video tracks should
+ // be subtitles; force-enable the first subs track
+ [subtitlesArray[1] setEnabled:YES];
+ }
+ }
+}
+
+- (void)_startMovieTimer
+{
+ if (!self.timer)
+ {
+ self.timer = [NSTimer scheduledTimerWithTimeInterval:0.09 target:self
+ selector:@selector(_timerFired:)
+ userInfo:nil repeats:YES];
+ }
+}
+
+- (void)_stopMovieTimer
+{
+ [self.timer invalidate];
+ self.timer = nil;
+}
+
+- (void)_timerFired:(NSTimer *)timer
+{
+ for (HBQTKitPlayerPeriodicObserver *observer in self.periodicObservers)
+ {
+ [observer postNotification:self.currentTime];
+ }
+}
+
+#pragma mark Public properties
+
+- (NSArray<HBPlayerTrack *> *)tracksWithMediaType:(NSString *)mediaType
+{
+ NSMutableArray *result = [NSMutableArray array];
+ NSArray<QTTrack *> *tracks = [self.movie tracksOfMediaType:mediaType];
+ for (QTTrack *track in tracks)
+ {
+ NSNumber *trackID = [track attributeForKey:QTTrackIDAttribute];
+ NSString *name = NSLocalizedString(@"Unknown", nil);
+
+ if ([track respondsToSelector:@selector(isoLanguageCodeAsString)])
+ {
+ NSString *language = [track isoLanguageCodeAsString];
+ name = [HBUtilities languageCodeForIso6392Code:language];
+ }
+ BOOL enabled = [[track attributeForKey:QTTrackEnabledAttribute] boolValue];
+
+ HBPlayerTrack *playerTrack = [[HBPlayerTrack alloc] initWithTrackName:name object:trackID enabled:enabled];
+
+ [result addObject:playerTrack];
+ }
+ return result;
+}
+
+@synthesize audioTracks = _audioTracks;
+
+- (NSArray<HBPlayerTrack *> *)audioTracks
+{
+ if (_audioTracks == nil)
+ {
+ _audioTracks = [self tracksWithMediaType:QTMediaTypeSound];
+ }
+ return _audioTracks;
+}
+
+@synthesize subtitlesTracks = _subtitlesTracks;
+
+- (NSArray<HBPlayerTrack *> *)subtitlesTracks
+{
+ if (_subtitlesTracks == nil)
+ {
+ _subtitlesTracks = [self tracksWithMediaType:QTMediaTypeSubtitle];
+ }
+ return _subtitlesTracks;
+}
+
+- (void)_enableTrack:(HBPlayerTrack *)playerTrack mediaType:(NSString *)mediaType
+{
+ NSArray<QTTrack *> *tracks = [self.movie tracksOfMediaType:mediaType];
+ for (QTTrack *track in tracks)
+ {
+ NSNumber *trackID = [track attributeForKey:QTTrackIDAttribute];
+
+ if ([trackID isEqualTo:playerTrack.representedObject])
+ {
+ [track setEnabled:YES];
+ }
+ else
+ {
+ [track setEnabled:NO];
+ }
+ }
+}
+
+- (void)enableAudioTrack:(HBPlayerTrack *)playerTrack
+{
+ for (HBPlayerTrack *track in self.audioTracks)
+ {
+ track.enabled = NO;
+ }
+ playerTrack.enabled = YES;
+ [self _enableTrack:playerTrack mediaType:QTMediaTypeSound];
+}
+
+- (void)enableSubtitlesTrack:(HBPlayerTrack *)playerTrack
+{
+ for (HBPlayerTrack *track in self.subtitlesTracks)
+ {
+ track.enabled = NO;
+ }
+ playerTrack.enabled = YES;
+ [self _enableTrack:playerTrack mediaType:QTMediaTypeSubtitle];
+}
+
+- (NSTimeInterval)duration
+{
+ QTTime duration = [self.movie duration];
+ return (double)duration.timeValue / (double)duration.timeScale;;
+}
+
+- (NSTimeInterval)currentTime
+{
+ QTTime time = [self.movie currentTime];
+ return (double)time.timeValue / (double)time.timeScale;;
+}
+
+- (void)setCurrentTime:(NSTimeInterval)value
+{
+ long timeScale = [[self.movie attributeForKey:QTMovieTimeScaleAttribute] longValue];
+ [self.movie setCurrentTime:QTMakeTime((long long)value * timeScale, timeScale)];
+ [self _timerFired:nil];
+}
+
+- (void)setRate:(float)rate
+{
+ self.movie.rate = rate;
+}
+
+- (float)rate
+{
+ return self.movie.rate;
+}
+
+- (float)volume
+{
+ return self.movie.volume;
+}
+
+- (void)setVolume:(float)volume
+{
+ self.movie.volume = volume;
+}
+
+#pragma mark public methods
+
+- (void)loadPlayableValueAsynchronouslyWithCompletionHandler:(nullable void (^)(void))handler;
+{
+ if (self.playable)
+ {
+ handler();
+ }
+ else
+ {
+ [self.playableObservers addObject:handler];
+ }
+}
+
+- (id)addPeriodicTimeObserverUsingBlock:(void (^)(NSTimeInterval time))block
+{
+ HBQTKitPlayerPeriodicObserver *observer = [[HBQTKitPlayerPeriodicObserver alloc] init];
+ observer.block = block;
+ [self.periodicObservers addObject:observer];
+
+ [self _startMovieTimer];
+
+ return observer;
+}
+
+- (void)removeTimeObserver:(id)observer
+{
+ [self.periodicObservers removeObject:observer];
+}
+
+- (id)addRateObserverUsingBlock:(void (^)(void))block
+{
+ HBQTKitPlayerRateObserver *observer = [[HBQTKitPlayerRateObserver alloc] init];
+ observer.block = block;
+ [self.rateObservers addObject:observer];
+
+ return observer;
+}
+
+- (void)removeRateObserver:(id)observer
+{
+ [self.rateObservers removeObject:observer];
+}
+
+- (void)play
+{
+ [self.movie play];
+ [self _startMovieTimer];
+}
+
+- (void)pause
+{
+ [self.movie stop];
+ [self _stopMovieTimer];
+}
+
+- (void)gotoBeginning
+{
+ [self.movie gotoBeginning];
+ [self _timerFired:nil];
+}
+
+- (void)gotoEnd
+{
+ [self.movie gotoEnd];
+ [self _timerFired:nil];
+}
+
+- (void)stepForward
+{
+ [self.movie stepForward];
+ [self _timerFired:nil];
+}
+
+- (void)stepBackward
+{
+ [self.movie stepBackward];
+ [self _timerFired:nil];
+}
+
+@end