/* HBQTKitPlayer.m $
This file is part of the HandBrake source code.
Homepage: .
It may be used under the terms of the GNU General Public License. */
#import "HBQTKitPlayer.h"
#import
@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, copy) HBPeriodicObverser block;
- (void)postNotification:(NSTimeInterval)time;
@end
@implementation HBQTKitPlayerPeriodicObserver
- (void)postNotification:(NSTimeInterval)time
{
self.block(time);
}
@end
@interface HBQTKitPlayerRateObserver : NSObject
@property (nonatomic, copy) 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 *periodicObservers;
@property (nonatomic, strong) NSMutableSet *rateObservers;
@property (nonatomic, strong) NSMutableSet *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 *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 *)tracksWithMediaType:(NSString *)mediaType
{
NSMutableArray *result = [NSMutableArray array];
NSArray *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 *)audioTracks
{
if (_audioTracks == nil)
{
_audioTracks = [self tracksWithMediaType:QTMediaTypeSound];
}
return _audioTracks;
}
@synthesize subtitlesTracks = _subtitlesTracks;
- (NSArray *)subtitlesTracks
{
if (_subtitlesTracks == nil)
{
_subtitlesTracks = [self tracksWithMediaType:QTMediaTypeSubtitle];
}
return _subtitlesTracks;
}
- (void)_enableTrack:(HBPlayerTrack *)playerTrack mediaType:(NSString *)mediaType
{
NSArray *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