diff options
author | Damiano Galassi <[email protected]> | 2016-05-03 18:47:14 +0200 |
---|---|---|
committer | Damiano Galassi <[email protected]> | 2016-05-03 18:47:14 +0200 |
commit | e53308f9342d911a9d3e8f4d1eeba6e53fabc0ab (patch) | |
tree | f65f5390d9174276022790a308660e17d7a3601f | |
parent | 4ab21a0ca630dc4bac79149a6eec598921899fe5 (diff) |
MacGui: use AVFoundation as the first option for the preview playback. Add volume and tracks controls to the player hud.
33 files changed, 2389 insertions, 948 deletions
diff --git a/macosx/English.lproj/HBPictureViewController.xib b/macosx/English.lproj/HBPictureViewController.xib index 9d12a5738..6c4b0686f 100644 --- a/macosx/English.lproj/HBPictureViewController.xib +++ b/macosx/English.lproj/HBPictureViewController.xib @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10115" systemVersion="15E61b" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15F24b" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> <dependencies> <deployment identifier="macosx"/> <development version="6300" identifier="xcode"/> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10115"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/> </dependencies> <objects> <customObject id="-2" userLabel="File's Owner" customClass="HBPictureViewController"> @@ -50,7 +50,7 @@ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <size key="cellSize" width="90" height="20"/> <size key="intercellSpacing" width="2" height="3"/> - <buttonCell key="prototype" type="radio" title="Radio" imagePosition="left" alignment="left" controlSize="small" inset="2" id="S7K-vF-t1n" customClass="HBHUDButtonCell"> + <buttonCell key="prototype" type="radio" title="Radio" imagePosition="left" alignment="left" controlSize="small" inset="2" id="S7K-vF-t1n"> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <font key="font" metaFont="smallSystem"/> </buttonCell> @@ -678,7 +678,7 @@ Players will scale the image in order to achieve the specified aspect.</string> <stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" id="FwZ-6T-zJe"> <rect key="frame" x="151" y="281" width="15" height="22"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <stepperCell key="cell" controlSize="small" continuous="YES" alignment="left" increment="16" minValue="64" maxValue="59" doubleValue="64" id="NP7-P2-Qga"> + <stepperCell key="cell" controlSize="small" continuous="YES" alignment="left" increment="16" minValue="64" maxValue="59" doubleValue="59" id="NP7-P2-Qga"> <font key="font" metaFont="smallSystem"/> </stepperCell> <connections> @@ -952,7 +952,7 @@ Players will scale the image in order to achieve the specified aspect.</string> <stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" id="2s0-5k-fjU"> <rect key="frame" x="240" y="281" width="15" height="22"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <stepperCell key="cell" controlSize="small" continuous="YES" alignment="left" increment="16" minValue="64" maxValue="59" doubleValue="64" id="MwK-aS-Oaf"> + <stepperCell key="cell" controlSize="small" continuous="YES" alignment="left" increment="16" minValue="64" maxValue="59" doubleValue="59" id="MwK-aS-Oaf"> <font key="font" metaFont="smallSystem"/> </stepperCell> <connections> @@ -1192,17 +1192,17 @@ If a deinterlace filter is enabled, only frames that this filter finds to be int </popUpButtonCell> <connections> <accessibilityConnection property="title" destination="Mg1-Yq-F9S" id="nhI-oN-u5h"/> - <binding destination="-2" name="contentValues" keyPath="self.filters.combDetectionSettings" id="lKn-uj-nGl"/> - <binding destination="-2" name="selectedValue" keyPath="self.filters.combDetection" previousBinding="lKn-uj-nGl" id="XIZ-dC-cLu"> + <binding destination="-2" name="enabled" keyPath="self.filters" id="mJ8-zq-tQ8"> <dictionary key="options"> - <string key="NSValueTransformerName">HBCombDetectionTransformer</string> + <string key="NSValueTransformerName">NSIsNotNil</string> </dictionary> </binding> - <binding destination="-2" name="enabled" keyPath="self.filters" id="mJ8-zq-tQ8"> + <binding destination="-2" name="selectedValue" keyPath="self.filters.combDetection" previousBinding="lKn-uj-nGl" id="XIZ-dC-cLu"> <dictionary key="options"> - <string key="NSValueTransformerName">NSIsNotNil</string> + <string key="NSValueTransformerName">HBCombDetectionTransformer</string> </dictionary> </binding> + <binding destination="-2" name="contentValues" keyPath="self.filters.combDetectionSettings" id="lKn-uj-nGl"/> <outlet property="nextKeyView" destination="rPg-F2-gtl" id="46r-ZD-dTe"/> </connections> </popUpButton> @@ -1216,12 +1216,12 @@ If a deinterlace filter is enabled, only frames that this filter finds to be int </textFieldCell> <accessibility description="Custom interlace detection settings."/> <connections> + <binding destination="-2" name="value" keyPath="self.filters.combDetectionCustomString" id="pTK-PZ-3ZE"/> <binding destination="-2" name="hidden" keyPath="self.filters.customCombDetectionSelected" id="avq-Zl-5gA"> <dictionary key="options"> <string key="NSValueTransformerName">NSNegateBoolean</string> </dictionary> </binding> - <binding destination="-2" name="value" keyPath="self.filters.combDetectionCustomString" id="pTK-PZ-3ZE"/> </connections> </textField> </subviews> diff --git a/macosx/English.lproj/PicturePreview.xib b/macosx/English.lproj/PicturePreview.xib index 77013fe9f..3550b20f6 100644 --- a/macosx/English.lproj/PicturePreview.xib +++ b/macosx/English.lproj/PicturePreview.xib @@ -1,28 +1,13 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9052" systemVersion="15B30a" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15F24b" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> <dependencies> <deployment identifier="macosx"/> <development version="6300" identifier="xcode"/> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9052"/> - <plugIn identifier="com.apple.QTKitIBPlugin" version="9052"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/> </dependencies> <objects> <customObject id="-2" userLabel="File's Owner" customClass="HBPreviewController"> <connections> - <outlet property="fEncodingControlBox" destination="F8A-dU-Y1l" id="s29-SQ-WXg"/> - <outlet property="fInfoField" destination="38" id="73"/> - <outlet property="fMovieCreationProgressIndicator" destination="213" id="214"/> - <outlet property="fMovieInfoField" destination="372" id="374"/> - <outlet property="fMoviePlaybackControlBox" destination="COi-Ia-2yt" id="dcQ-Bp-1jG"/> - <outlet property="fMovieScrubberSlider" destination="341" id="371"/> - <outlet property="fMovieView" destination="207" id="208"/> - <outlet property="fPictureControlBox" destination="2me-4k-EDi" id="ABj-KD-Z3U"/> - <outlet property="fPictureSlider" destination="209" id="211"/> - <outlet property="fPlayPauseButton" destination="361" id="364"/> - <outlet property="fPreviewMovieLengthPopUp" destination="226" id="232"/> - <outlet property="fPreviewMovieStatusField" destination="223" id="225"/> - <outlet property="fScaleToScreenToggleButton" destination="275" id="yX0-fL-6J9"/> - <outlet property="fscaleInfoField" destination="280" id="282"/> <outlet property="previewView" destination="ooo-9X-9Al" id="als-Lt-aVz"/> <outlet property="window" destination="5" id="184"/> </connections> @@ -41,235 +26,8 @@ <customView id="ooo-9X-9Al" customClass="HBPreviewView"> <rect key="frame" x="0.0" y="0.0" width="500" height="360"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <animations/> - </customView> - <qtMovieView preservesAspectRatio="YES" id="207"> - <rect key="frame" x="0.0" y="0.0" width="500" height="360"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <animations/> - <color key="fillColor" red="0.80000000999999998" green="0.80000000999999998" blue="0.80000000999999998" alpha="1" colorSpace="calibratedRGB"/> - </qtMovieView> - <customView id="2me-4k-EDi" userLabel="Picture Controls" customClass="HBHUDView"> - <rect key="frame" x="20" y="136" width="460" height="100"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> - <subviews> - <button toolTip="Encode And Play Back A Live Preview At Your Current Settings" verticalHuggingPriority="750" id="215"> - <rect key="frame" x="17" y="11" width="80" height="16"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> - <animations/> - <buttonCell key="cell" type="push" title="Live Preview" bezelStyle="rounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="216"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="miniSystem"/> - </buttonCell> - <connections> - <action selector="createMoviePreview:" target="-2" id="218"/> - </connections> - </button> - <button toolTip="Show Picture Settings Inspector" verticalHuggingPriority="750" id="271"> - <rect key="frame" x="378" y="11" width="61" height="16"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <animations/> - <buttonCell key="cell" type="push" title="Settings" bezelStyle="rounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="272"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="miniSystem"/> - </buttonCell> - <connections> - <action selector="showPictureSettings:" target="-2" id="274"/> - </connections> - </button> - <button toolTip="Scale Preview To Screen" verticalHuggingPriority="750" id="275"> - <rect key="frame" x="276" y="11" width="96" height="16"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <animations/> - <buttonCell key="cell" type="push" title="Scale To Screen" bezelStyle="rounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="276"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="miniSystem"/> - </buttonCell> - <connections> - <action selector="toggleScaleToScreen:" target="-2" id="279"/> - </connections> - </button> - <textField verticalHuggingPriority="750" id="234"> - <rect key="frame" x="105" y="14" width="48" height="11"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <animations/> - <textFieldCell key="cell" controlSize="mini" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Duration:" id="235"> - <font key="font" metaFont="miniSystemBold"/> - <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/> - <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> - </textFieldCell> - </textField> - <textField verticalHuggingPriority="750" id="q81-Om-Azd"> - <rect key="frame" x="211" y="14" width="21" height="11"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <animations/> - <textFieldCell key="cell" controlSize="mini" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="sec" id="APs-l9-qFj"> - <font key="font" metaFont="miniSystemBold"/> - <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/> - <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> - </textFieldCell> - </textField> - <popUpButton toolTip="Select The Length Of Live Preview to Encode" verticalHuggingPriority="750" id="226"> - <rect key="frame" x="158" y="12" width="49" height="15"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <animations/> - <popUpButtonCell key="cell" type="push" title="240" bezelStyle="rounded" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" inset="2" selectedItem="231" id="227"> - <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="miniSystem"/> - <menu key="menu" title="OtherViews" id="228"> - <items> - <menuItem title="240" state="on" id="231"/> - </items> - </menu> - </popUpButtonCell> - <connections> - <action selector="previewDurationPopUpChanged:" target="-2" id="233"/> - </connections> - </popUpButton> - <slider verticalHuggingPriority="750" id="209"> - <rect key="frame" x="18" y="39" width="420" height="16"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> - <animations/> - <sliderCell key="cell" controlSize="mini" continuous="YES" alignment="left" maxValue="9" tickMarkPosition="above" numberOfTickMarks="10" allowsTickMarkValuesOnly="YES" sliderType="linear" id="210"> - <font key="font" metaFont="miniSystem"/> - </sliderCell> - <connections> - <action selector="pictureSliderChanged:" target="-2" id="212"/> - </connections> - </slider> - <textField verticalHuggingPriority="750" id="38"> - <rect key="frame" x="15" y="55" width="430" height="20"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> - <animations/> - <textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" alignment="left" id="165"> - <font key="font" metaFont="smallSystemBold"/> - <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/> - <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> - </textFieldCell> - </textField> - <textField verticalHuggingPriority="750" id="280"> - <rect key="frame" x="15" y="77" width="426" height="12"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> - <animations/> - <textFieldCell key="cell" controlSize="mini" sendsActionOnEndEditing="YES" alignment="left" id="281"> - <font key="font" metaFont="miniSystemBold"/> - <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/> - <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> - </textFieldCell> - </textField> - </subviews> - <animations/> - </customView> - <customView hidden="YES" id="COi-Ia-2yt" userLabel="Playback Controls" customClass="HBHUDView"> - <rect key="frame" x="20" y="32" width="460" height="100"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> - <subviews> - <slider verticalHuggingPriority="750" id="341"> - <rect key="frame" x="18" y="17" width="337" height="21"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> - <animations/> - <sliderCell key="cell" continuous="YES" alignment="left" maxValue="100" tickMarkPosition="above" sliderType="linear" id="346"/> - <connections> - <action selector="previewScrubberChanged:" target="-2" id="384"/> - </connections> - </slider> - <button toolTip="Toggle Play/Pause" id="361"> - <rect key="frame" x="207" y="44" width="36" height="36"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <animations/> - <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="play-p" imagePosition="only" alignment="center" alternateImage="pause-p" imageScaling="proportionallyDown" id="362"> - <behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/> - <font key="font" metaFont="system"/> - </buttonCell> - <connections> - <action selector="toggleMoviePreviewPlayPause:" target="-2" id="370"/> - </connections> - </button> - <button toolTip="Go To Beginning" id="375"> - <rect key="frame" x="165" y="49" width="32" height="26"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <animations/> - <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="prev-p" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" id="376"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="system"/> - </buttonCell> - <connections> - <action selector="moviePlaybackGoToBeginning:" target="-2" id="382"/> - </connections> - </button> - <button toolTip="Go To End" id="378"> - <rect key="frame" x="253" y="49" width="32" height="26"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <animations/> - <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="next-p" imagePosition="only" alignment="center" controlSize="mini" imageScaling="proportionallyDown" id="379"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="miniSystem"/> - </buttonCell> - <connections> - <action selector="moviePlaybackGoToEnd:" target="-2" id="383"/> - </connections> - </button> - <button toolTip="Show Still Previews" verticalHuggingPriority="750" id="365"> - <rect key="frame" x="350" y="54" width="81" height="16"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> - <animations/> - <buttonCell key="cell" type="push" title="Still Previews" bezelStyle="rounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="366"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="miniSystem"/> - </buttonCell> - <connections> - <action selector="showPicturesPreview:" target="-2" id="396"/> - </connections> - </button> - <textField verticalHuggingPriority="750" id="372"> - <rect key="frame" x="349" y="20" width="94" height="14"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> - <animations/> - <textFieldCell key="cell" controlSize="mini" sendsActionOnEndEditing="YES" alignment="right" title="00:00:00" id="373"> - <font key="font" metaFont="smallSystemBold"/> - <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/> - <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> - </textFieldCell> - </textField> - </subviews> - <animations/> - </customView> - <customView hidden="YES" id="F8A-dU-Y1l" userLabel="Encoding Controls" customClass="HBHUDView"> - <rect key="frame" x="20" y="240" width="460" height="100"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> - <subviews> - <textField verticalHuggingPriority="750" id="223"> - <rect key="frame" x="17" y="60" width="425" height="14"/> - <autoresizingMask key="autoresizingMask"/> - <animations/> - <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Preparing Preview" id="224"> - <font key="font" metaFont="smallSystemBold"/> - <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/> - <color key="backgroundColor" red="0.90196085000000004" green="0.90196085000000004" blue="0.90196085000000004" alpha="0.0" colorSpace="deviceRGB"/> - </textFieldCell> - </textField> - <progressIndicator verticalHuggingPriority="750" maxValue="1" bezeled="NO" indeterminate="YES" controlSize="small" style="bar" id="213"> - <rect key="frame" x="20" y="27" width="346" height="12"/> - <autoresizingMask key="autoresizingMask"/> - <animations/> - </progressIndicator> - <button toolTip="Cancel Live Preview Encode" verticalHuggingPriority="750" id="261"> - <rect key="frame" x="383" y="25" width="58" height="16"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> - <animations/> - <buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="262"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="miniSystem"/> - </buttonCell> - <connections> - <action selector="cancelCreateMoviePreview:" target="-2" id="395"/> - </connections> - </button> - </subviews> - <animations/> </customView> </subviews> - <animations/> </view> <connections> <outlet property="delegate" destination="-2" id="7iq-HC-WuX"/> @@ -277,10 +35,4 @@ <point key="canvasLocation" x="-182" y="-40"/> </window> </objects> - <resources> - <image name="next-p" width="48" height="48"/> - <image name="pause-p" width="48" height="48"/> - <image name="play-p" width="48" height="48"/> - <image name="prev-p" width="48" height="48"/> - </resources> </document> diff --git a/macosx/HBAVPlayer.h b/macosx/HBAVPlayer.h new file mode 100644 index 000000000..d8593efc4 --- /dev/null +++ b/macosx/HBAVPlayer.h @@ -0,0 +1,28 @@ +/* HBAVPlayer.h $ + + 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 <Foundation/Foundation.h> +#import <QuartzCore/QuartzCore.h> + +#import "HBPlayer.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface HBAVPlayer : NSObject <HBPlayer> + +@property (nonatomic, readonly) CALayer *layer; + +@property (nonatomic, readonly) NSTimeInterval duration; +@property (nonatomic) NSTimeInterval currentTime; + +@property (nonatomic) float rate; +@property (nonatomic) float volume; + +@property (nonatomic, readonly, getter=isPlayable) BOOL playable; + +@end + +NS_ASSUME_NONNULL_END diff --git a/macosx/HBAVPlayer.m b/macosx/HBAVPlayer.m new file mode 100644 index 000000000..ef72734c2 --- /dev/null +++ b/macosx/HBAVPlayer.m @@ -0,0 +1,367 @@ +/* HBAVPlayer.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 "HBAVPlayer.h" + +@import AVFoundation; +@import HandBrakeKit; + +static void *HBAVPlayerRateContext = &HBAVPlayerRateContext; +static void *HBAVPlayerItemStatusContext = &HBAVPlayerItemStatusContext; + +typedef void (^HBRateObverser)(void); +typedef void (^HBPlayableObverser)(void); + +@interface HBAVPlayerRateObserver : NSObject + +@property (nonatomic) HBRateObverser block; + +- (void)postNotification; + +@end + +@implementation HBAVPlayerRateObserver + +- (void)postNotification; +{ + self.block(); +} + +@end + +@interface HBAVPlayer () + +@property (nonatomic, strong) AVAsset *movie; +@property (nonatomic, strong) AVPlayer *player; + +@property (nonatomic, strong) NSMutableSet<HBAVPlayerRateObserver *> *rateObservers; +@property (nonatomic, strong) NSMutableSet<HBPlayableObverser> *playableObservers; + +@property (nonatomic, readwrite) BOOL playable; + +@end + +@implementation HBAVPlayer + +- (instancetype)initWithURL:(NSURL *)url +{ + self = [super init]; + + if (self) + { + _movie = [AVAsset assetWithURL:url];; + + if (!_movie) + { + return nil; + } + + _player = [[AVPlayer alloc] init]; + _layer = [CALayer layer]; + + if (!_layer || !_player) + { + return nil; + } + + _rateObservers = [NSMutableSet set]; + _playableObservers = [NSMutableSet set]; + + [self addObserver:self forKeyPath:@"player.rate" options:NSKeyValueObservingOptionNew context:HBAVPlayerRateContext]; + [self addObserver:self forKeyPath:@"player.currentItem.status" options:NSKeyValueObservingOptionNew context:HBAVPlayerItemStatusContext]; + + NSArray *assetKeysToLoadAndTest = @[@"playable"]; + [_movie loadValuesAsynchronouslyForKeys:assetKeysToLoadAndTest completionHandler:^(void) { + + // The asset invokes its completion handler on an arbitrary queue when loading is complete. + // Because we want to access our AVPlayer in our ensuing set-up, we must dispatch our handler to the main queue. + dispatch_async(dispatch_get_main_queue(), ^(void) { + [self _setUpPlaybackOfAsset:_movie withKeys:assetKeysToLoadAndTest]; + }); + + }]; + } + + return self; +} + +- (void)dealloc +{ + @try + { + [self removeObserver:self forKeyPath:@"player.rate"]; + [self removeObserver:self forKeyPath:@"player.currentItem.status"]; + } + @catch (NSException *exception) {} +} + +- (void)_setUpPlaybackOfAsset:(AVAsset *)asset withKeys:(NSArray *)keys +{ + // First test whether the values of each of the keys we need have been successfully loaded. + for (NSString *key in keys) + { + NSError *error = nil; + + if ([asset statusOfValueForKey:key error:&error] == AVKeyValueStatusFailed) + { + self.playable = NO; + return; + } + + if (!asset.isPlayable) + { + self.playable = NO; + return; + } + + AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset]; + [self.player replaceCurrentItemWithPlayerItem:playerItem]; + + AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; + playerLayer.frame = self.layer.bounds; + playerLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; + + [self.layer addSublayer:playerLayer]; + } +} + +- (void)setPlayable:(BOOL)playable +{ + _playable = playable; + + for (HBPlayableObverser block in self.playableObservers) + { + block(); + } + [self.playableObservers removeAllObjects]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if (context == HBAVPlayerItemStatusContext) + { + AVPlayerStatus status = [change[NSKeyValueChangeNewKey] integerValue]; + switch (status) + { + case AVPlayerItemStatusUnknown: + break; + case AVPlayerItemStatusReadyToPlay: + self.playable = YES; + break; + case AVPlayerItemStatusFailed: + self.playable = YES; + break; + } + + } + else if (context == HBAVPlayerRateContext) + { + for (HBAVPlayerRateObserver *observer in self.rateObservers) + { + [observer postNotification]; + } + } + else + { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +#pragma mark Public properties + +- (NSArray<HBPlayerTrack *> *)tracksWithMediaType:(NSString *)mediaType +{ + NSMutableArray *result = [NSMutableArray array]; + NSArray<AVPlayerItemTrack *> *tracks = self.player.currentItem.tracks; + + for (AVPlayerItemTrack *track in tracks) + { + AVAssetTrack *assetTrack = track.assetTrack; + if ([assetTrack.mediaType isEqualToString:mediaType]) + { + NSString *name = [HBUtilities languageCodeForIso6392Code:assetTrack.languageCode]; + HBPlayerTrack *playerTrack = [[HBPlayerTrack alloc] initWithTrackName:name object:@(assetTrack.trackID) enabled:track.isEnabled]; + [result addObject:playerTrack]; + } + } + return result; +} + +@synthesize audioTracks = _audioTracks; + +- (NSArray<HBPlayerTrack *> *)audioTracks +{ + if (_audioTracks == nil) + { + _audioTracks = [self tracksWithMediaType:AVMediaTypeAudio]; + } + return _audioTracks; +} + +@synthesize subtitlesTracks = _subtitlesTracks; + +- (NSArray<HBPlayerTrack *> *)subtitlesTracks +{ + if (_subtitlesTracks == nil) + { + _subtitlesTracks = [self tracksWithMediaType:AVMediaTypeSubtitle]; + } + return _subtitlesTracks; +} + +- (void)_enableTrack:(HBPlayerTrack *)playerTrack mediaType:(NSString *)mediaType +{ + NSArray<AVPlayerItemTrack *> *tracks = self.player.currentItem.tracks; + for (AVPlayerItemTrack *track in tracks) + { + if ([track.assetTrack.mediaType isEqualToString:mediaType]) + { + if (track.assetTrack.trackID == [playerTrack.representedObject integerValue]) + { + track.enabled = YES; + } + else + { + track.enabled = NO; + } + } + } +} + +- (void)enableAudioTrack:(HBPlayerTrack *)playerTrack +{ + for (HBPlayerTrack *track in self.audioTracks) + { + track.enabled = NO; + } + playerTrack.enabled = YES; + [self _enableTrack:playerTrack mediaType:AVMediaTypeAudio]; +} + +- (void)enableSubtitlesTrack:(HBPlayerTrack *)playerTrack +{ + for (HBPlayerTrack *track in self.subtitlesTracks) + { + track.enabled = NO; + } + playerTrack.enabled = YES; + [self _enableTrack:playerTrack mediaType:AVMediaTypeSubtitle]; +} + +- (NSTimeInterval)duration +{ + return CMTimeGetSeconds(self.movie.duration); +} + +- (NSTimeInterval)currentTime +{ + return CMTimeGetSeconds(self.player.currentTime); +} + +- (void)setCurrentTime:(NSTimeInterval)value +{ + [self.player seekToTime:CMTimeMake((int64_t)(value * 1000), 1000) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; +} + +- (void)setRate:(float)rate +{ + self.player.rate = rate; +} + +- (float)rate +{ + return self.player.rate; +} + +- (float)volume +{ + return self.player.volume; +} + +- (void)setVolume:(float)volume +{ + self.player.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 +{ + CMTime interval = CMTimeMake(100, 1000); + + id observer = [self.player addPeriodicTimeObserverForInterval:interval queue:dispatch_get_main_queue() usingBlock:^(CMTime time) { + block(0); + }]; + + return observer; +} + +- (void)removeTimeObserver:(id)observer +{ + [self.player removeTimeObserver:observer]; +} + +- (id)addRateObserverUsingBlock:(void (^)(void))block +{ + HBAVPlayerRateObserver *observer = [[HBAVPlayerRateObserver alloc] init]; + observer.block = block; + [self.rateObservers addObject:observer]; + + return observer; +} + +- (void)removeRateObserver:(id)observer +{ + [self.rateObservers removeObject:observer]; +} + +- (void)play +{ + [self.player play]; +} + +- (void)pause +{ + [self.player pause]; +} + +- (void)gotoBeginning +{ + [self.player seekToTime:kCMTimeZero toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; +} + +- (void)gotoEnd +{ + [self.player seekToTime:self.player.currentItem.duration toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; +} + +- (void)stepForward +{ + CMTime frameTime = CMTimeMakeWithSeconds(1.0/30.0, self.player.currentItem.duration.timescale); + CMTime forwardTime = CMTimeAdd([self.player.currentItem currentTime], frameTime); + [self.player seekToTime:forwardTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; +} + +- (void)stepBackward +{ + CMTime frameTime = CMTimeMakeWithSeconds(-1.0/30.0, self.player.currentItem.duration.timescale); + CMTime backwardTime = CMTimeAdd([self.player.currentItem currentTime], frameTime); + [self.player seekToTime:backwardTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; +} + +@end diff --git a/macosx/HBEncodingProgressHUDController.h b/macosx/HBEncodingProgressHUDController.h new file mode 100644 index 000000000..e147d09c4 --- /dev/null +++ b/macosx/HBEncodingProgressHUDController.h @@ -0,0 +1,23 @@ +/* HBEncodingProgressHUDController.h $ + + 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 <Cocoa/Cocoa.h> +#import "HBHUD.h" + +@protocol HBEncodingProgressHUDControllerDelegate <NSObject> + +- (void)cancelEncoding; + +@end + +@interface HBEncodingProgressHUDController : NSViewController <HBHUD> + +@property (nonatomic, nullable, assign) id<HBEncodingProgressHUDControllerDelegate> delegate; + +@property (nonatomic, nonnull) NSString *info; +@property (nonatomic) double progress; + +@end diff --git a/macosx/HBEncodingProgressHUDController.m b/macosx/HBEncodingProgressHUDController.m new file mode 100644 index 000000000..4ac16e1ea --- /dev/null +++ b/macosx/HBEncodingProgressHUDController.m @@ -0,0 +1,63 @@ +/* HBEncodingProgressHUDController.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 "HBEncodingProgressHUDController.h" + +@interface HBEncodingProgressHUDController () + +@property (weak) IBOutlet NSProgressIndicator *progressIndicator; +@property (weak) IBOutlet NSTextField *infoLabel; + +@end + +@implementation HBEncodingProgressHUDController + +- (NSString *)nibName +{ + return @"HBEncodingProgressHUDController"; +} + +- (void)loadView +{ + [super loadView]; + + if (NSClassFromString(@"NSVisualEffectView") == NO) + { + self.infoLabel.textColor = [NSColor whiteColor]; + } +} + +- (BOOL)canBeHidden +{ + return NO; +} + +- (void)setInfo:(NSString *)info +{ + self.infoLabel.stringValue = info; +} + +- (void)setProgress:(double)progress +{ + self.progressIndicator.doubleValue = progress; +} + +- (IBAction)cancelEncoding:(id)sender +{ + [self.delegate cancelEncoding]; +} + +- (BOOL)HB_keyDown:(NSEvent *)event +{ + return NO; +} + +- (BOOL)HB_scrollWheel:(NSEvent *)theEvent +{ + return NO; +} + +@end diff --git a/macosx/HBEncodingProgressHUDController.xib b/macosx/HBEncodingProgressHUDController.xib new file mode 100644 index 000000000..6f3f919ed --- /dev/null +++ b/macosx/HBEncodingProgressHUDController.xib @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15F28b" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> + <dependencies> + <deployment identifier="macosx"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="HBEncodingProgressHUDController"> + <connections> + <outlet property="infoLabel" destination="eKh-n7-BCx" id="Ekf-s1-gAB"/> + <outlet property="progressIndicator" destination="oJT-Rc-VeH" id="09U-Ur-Aru"/> + <outlet property="view" destination="Bz7-r6-NCQ" id="hXI-Gx-WVV"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <customView hidden="YES" id="Bz7-r6-NCQ" userLabel="Encoding Controls" customClass="HBHUDView"> + <rect key="frame" x="0.0" y="0.0" width="460" height="100"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> + <subviews> + <progressIndicator verticalHuggingPriority="750" fixedFrame="YES" maxValue="1" bezeled="NO" controlSize="small" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="oJT-Rc-VeH"> + <rect key="frame" x="20" y="27" width="346" height="12"/> + </progressIndicator> + <button toolTip="Cancel Live Preview Encode" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SEa-H6-T26"> + <rect key="frame" x="383" y="25" width="58" height="16"/> + <buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Ha0-iE-RLa"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="miniSystem"/> + </buttonCell> + <connections> + <action selector="cancelEncoding:" target="-2" id="XDk-r6-Ihc"/> + </connections> + </button> + <textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="eKh-n7-BCx"> + <rect key="frame" x="17" y="60" width="425" height="14"/> + <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Preparing Preview" id="tM8-gj-kBo"> + <font key="font" metaFont="smallSystemBold"/> + <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" red="0.90196085000000004" green="0.90196085000000004" blue="0.90196085000000004" alpha="0.0" colorSpace="deviceRGB"/> + </textFieldCell> + </textField> + </subviews> + <point key="canvasLocation" x="55" y="349"/> + </customView> + </objects> +</document> diff --git a/macosx/HBHUD.h b/macosx/HBHUD.h new file mode 100644 index 000000000..d10e42f69 --- /dev/null +++ b/macosx/HBHUD.h @@ -0,0 +1,21 @@ +/* HBHUD.h $ + + 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 <Foundation/Foundation.h> + +@protocol HBHUD <NSObject> + +/// Whether the hud can be hidden or not; +- (BOOL)canBeHidden; + +// Responder chains is nice and good, but NSViewController +// are not automatically insterted in the responder chain prior 10.10 +// and are removed when the view is hidden, so let's deliver the +// events manually. + +- (BOOL)HB_keyDown:(NSEvent *)event; +- (BOOL)HB_scrollWheel:(NSEvent *)theEvent; + +@end diff --git a/macosx/HBHUDButtonCell.m b/macosx/HBHUDButtonCell.m index 3464a7499..a5e4119ec 100644 --- a/macosx/HBHUDButtonCell.m +++ b/macosx/HBHUDButtonCell.m @@ -10,9 +10,14 @@ - (NSRect)drawTitle:(NSAttributedString *)title withFrame:(NSRect)frame inView:(NSView *)controlView { - NSAttributedString *attrLabel = [[NSAttributedString alloc] initWithString:[title string] + NSAttributedString *attrLabel = title; + + if (!NSClassFromString(@"NSVisualEffectView")) + { + attrLabel = [[NSAttributedString alloc] initWithString:[title string] attributes:@{ NSFontAttributeName:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:self.controlSize]], NSForegroundColorAttributeName: [NSColor whiteColor]}]; + } return [super drawTitle:attrLabel withFrame:frame inView:controlView]; } diff --git a/macosx/HBHUDView.m b/macosx/HBHUDView.m index e4030f743..1d6079d96 100644 --- a/macosx/HBHUDView.m +++ b/macosx/HBHUDView.m @@ -6,37 +6,37 @@ #import "HBHUDView.h" -@implementation HBHUDView +@interface HBHUDVisualEffectsView : NSVisualEffectView +@end + +@implementation HBHUDVisualEffectsView -+ (void)setupNewStyleHUD:(NSVisualEffectView *)view +- (instancetype)initWithFrame:(NSRect)frame { - [view setWantsLayer:YES]; - [view.layer setCornerRadius:4]; + self = [super initWithFrame:frame]; - [view setBlendingMode:NSVisualEffectBlendingModeWithinWindow]; - [view setMaterial:NSVisualEffectMaterialDark]; - [view setState:NSVisualEffectStateActive]; + if (self) + { + self.wantsLayer = YES; + self.layer.cornerRadius = 4; + + self.blendingMode = NSVisualEffectBlendingModeWithinWindow; + self.material = NSVisualEffectMaterialDark; + self.state = NSVisualEffectStateActive; - [view setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]]; + self.appearance = [NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]; + } + return self; } -- (void)drawRect:(NSRect)dirtyRect +- (BOOL)acceptsFirstResponder { - NSGraphicsContext *theContext = [NSGraphicsContext currentContext]; - [theContext saveGraphicsState]; - - NSRect rect = NSMakeRect(0.0, 0.0, [self frame].size.width, [self frame].size.height); - - // Draw a standard HUD with black transparent background and white border. - [[NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:0.6] setFill]; - [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, 1, 1) xRadius:14.0 yRadius:14.0] fill]; + return YES; +} - [[NSColor whiteColor] setStroke]; - [NSBezierPath setDefaultLineWidth:2.0]; - [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, 1, 1) xRadius:14.0 yRadius:14.0] stroke]; +@end - [theContext restoreGraphicsState]; -} +@implementation HBHUDView - (instancetype)initWithFrame:(NSRect)frame { @@ -44,11 +44,7 @@ { // If NSVisualEffectView class is loaded // release ourself and return a NSVisualEffectView instance instead. - self = [[NSClassFromString(@"NSVisualEffectView") alloc] initWithFrame:frame]; - if (self) - { - [HBHUDView setupNewStyleHUD:(NSVisualEffectView *)self]; - } + self = (HBHUDView *)[[HBHUDVisualEffectsView alloc] initWithFrame:frame]; } else { @@ -58,4 +54,27 @@ return self; } +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + NSGraphicsContext *theContext = [NSGraphicsContext currentContext]; + [theContext saveGraphicsState]; + + NSRect rect = NSMakeRect(0.0, 0.0, self.frame.size.width, self.frame.size.height); + + // Draw a standard HUD with black transparent background and white border. + [[NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:0.6] setFill]; + [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, 1, 1) xRadius:14.0 yRadius:14.0] fill]; + + [[NSColor whiteColor] setStroke]; + [NSBezierPath setDefaultLineWidth:2.0]; + [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, 1, 1) xRadius:14.0 yRadius:14.0] stroke]; + + [theContext restoreGraphicsState]; +} + @end
\ No newline at end of file diff --git a/macosx/HBPictureHUDController.h b/macosx/HBPictureHUDController.h new file mode 100644 index 000000000..1a11d2063 --- /dev/null +++ b/macosx/HBPictureHUDController.h @@ -0,0 +1,30 @@ +/* HBPictureHUDController.h $ + + 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 <Cocoa/Cocoa.h> +#import "HBHUD.h" + +@protocol HBPictureHUDControllerDelegate <NSObject> + +- (void)displayPreviewAtIndex:(NSUInteger)idx; +- (void)toggleScaleToScreen; +- (void)showPictureSettings; +- (void)createMoviePreviewWithPictureIndex:(NSUInteger)index duration:(NSUInteger)duration; + +@end + +@interface HBPictureHUDController : NSViewController <HBHUD> + +@property (nonatomic, nullable, assign) id<HBPictureHUDControllerDelegate> delegate; + +@property (nonatomic, nonnull) NSString *info; +@property (nonatomic, nonnull) NSString *scale; + +@property (nonatomic) NSUInteger pictureCount; +@property (nonatomic) NSUInteger selectedIndex; + +@end diff --git a/macosx/HBPictureHUDController.m b/macosx/HBPictureHUDController.m new file mode 100644 index 000000000..741e6d008 --- /dev/null +++ b/macosx/HBPictureHUDController.m @@ -0,0 +1,163 @@ +/* HBPictureHUDController.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 "HBPictureHUDController.h" + +@interface HBPictureHUDController () + +@property (weak) IBOutlet NSTextField *scaleLabel; +@property (weak) IBOutlet NSTextField *infoLabel; + +@property (weak) IBOutlet NSSlider *slider; + +@property (weak) IBOutlet NSPopUpButton *durationPopUp; +@property (weak) IBOutlet NSButton *scaleToScreenButton; + +@property (nonatomic) BOOL fitToView; + +@end + +@implementation HBPictureHUDController + +- (NSString *)nibName +{ + return @"HBPictureHUDController"; +} + +- (void)loadView +{ + [super loadView]; + + if (NSClassFromString(@"NSVisualEffectView") == NO) + { + self.scaleLabel.textColor = [NSColor whiteColor]; + self.infoLabel.textColor = [NSColor whiteColor]; + } + + // we set the preview length popup in seconds + [self.durationPopUp removeAllItems]; + [self.durationPopUp addItemsWithTitles:@[@"15", @"30", @"45", @"60", @"90", + @"120", @"150", @"180", @"210", @"240"]]; + + if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]) + { + [self.durationPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] + objectForKey:@"PreviewLength"]]; + } + if (!self.durationPopUp.selectedItem) + { + // currently hard set default to 15 seconds + [self.durationPopUp selectItemAtIndex:0]; + } +} + +- (BOOL)canBeHidden +{ + return YES; +} + +- (void)setPictureCount:(NSUInteger)pictureCount +{ + self.slider.numberOfTickMarks = pictureCount; + self.slider.maxValue = pictureCount - 1; + + if (self.selectedIndex > pictureCount) + { + self.selectedIndex = pictureCount - 1; + } +} + +- (NSUInteger)selectedIndex +{ + return self.slider.integerValue; +} + +- (void)setSelectedIndex:(NSUInteger)selectedIndex +{ + self.slider.integerValue = selectedIndex; +} + +- (void)setInfo:(NSString *)info +{ + self.infoLabel.stringValue = info; +} + +- (void)setScale:(NSString *)scale +{ + self.scaleLabel.stringValue = scale; +} + +- (IBAction)previewDurationPopUpChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:self.durationPopUp.titleOfSelectedItem forKey:@"PreviewLength"]; +} + +- (IBAction)pictureSliderChanged:(id)sender +{ + [self.delegate displayPreviewAtIndex:self.slider.integerValue]; +} + +- (IBAction)toggleScaleToScreen:(id)sender +{ + [self.delegate toggleScaleToScreen]; + if (self.fitToView == YES) + { + self.scaleToScreenButton.title = NSLocalizedString(@"Scale To Screen", nil); + } + else + { + self.scaleToScreenButton.title = NSLocalizedString(@"Actual Scale", nil); + } + self.fitToView = !self.fitToView; +} + +- (IBAction)showPictureSettings:(id)sender +{ + [self.delegate showPictureSettings]; +} + +- (IBAction)createMoviePreview:(id)sender +{ + [self.delegate createMoviePreviewWithPictureIndex:self.slider.integerValue duration:self.durationPopUp.titleOfSelectedItem.intValue]; +} + +- (BOOL)HB_keyDown:(NSEvent *)event +{ + unichar key = [event.charactersIgnoringModifiers characterAtIndex:0]; + if (key == NSLeftArrowFunctionKey) + { + self.slider.integerValue = self.selectedIndex > self.slider.minValue ? self.selectedIndex - 1 : self.selectedIndex; + [self.delegate displayPreviewAtIndex:self.slider.integerValue]; + return YES; + } + else if (key == NSRightArrowFunctionKey) + { + self.slider.integerValue = self.selectedIndex < self.slider.maxValue ? self.selectedIndex + 1 : self.selectedIndex; + [self.delegate displayPreviewAtIndex:self.slider.integerValue]; + return YES; + } + else + { + return NO; + } +} + +- (BOOL)HB_scrollWheel:(NSEvent *)theEvent +{ + if (theEvent.deltaY < 0) + { + self.slider.integerValue = self.selectedIndex < self.slider.maxValue ? self.selectedIndex + 1 : self.selectedIndex; + [self.delegate displayPreviewAtIndex:self.slider.integerValue]; + } + else if (theEvent.deltaY > 0) + { + self.slider.integerValue = self.selectedIndex > self.slider.minValue ? self.selectedIndex - 1 : self.selectedIndex; + [self.delegate displayPreviewAtIndex:self.slider.integerValue]; + } + return YES; +} + +@end diff --git a/macosx/HBPictureHUDController.xib b/macosx/HBPictureHUDController.xib new file mode 100644 index 000000000..fe3152960 --- /dev/null +++ b/macosx/HBPictureHUDController.xib @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15F28b" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> + <dependencies> + <deployment identifier="macosx"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="HBPictureHUDController"> + <connections> + <outlet property="durationPopUp" destination="ASA-X8-16P" id="Ti4-aX-nbp"/> + <outlet property="infoLabel" destination="MkW-KN-I1h" id="c2A-DS-cd1"/> + <outlet property="scaleLabel" destination="7UR-Zz-7iX" id="ukS-8e-xmA"/> + <outlet property="scaleToScreenButton" destination="12K-c3-Z7A" id="ISw-XG-Wg8"/> + <outlet property="slider" destination="wUk-SQ-GhS" id="xWn-kV-0gS"/> + <outlet property="view" destination="agq-EJ-miM" id="3Eb-l9-o2j"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <customView id="agq-EJ-miM" userLabel="Picture Controls" customClass="HBHUDView"> + <rect key="frame" x="0.0" y="0.0" width="460" height="100"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> + <subviews> + <button toolTip="Encode And Play Back A Live Preview At Your Current Settings" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="16b-R9-bBU"> + <rect key="frame" x="17" y="11" width="80" height="16"/> + <buttonCell key="cell" type="push" title="Live Preview" bezelStyle="rounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="vkh-GY-5qx"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="miniSystem"/> + </buttonCell> + <connections> + <action selector="createMoviePreview:" target="-2" id="rhf-gy-aMT"/> + </connections> + </button> + <button toolTip="Show Picture Settings Inspector" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mV7-hU-tMt"> + <rect key="frame" x="378" y="11" width="61" height="16"/> + <buttonCell key="cell" type="push" title="Settings" bezelStyle="rounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="NWd-Lq-c1A"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="miniSystem"/> + </buttonCell> + <connections> + <action selector="showPictureSettings:" target="-2" id="aDd-cp-S1k"/> + </connections> + </button> + <button toolTip="Scale Preview To Screen" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="12K-c3-Z7A"> + <rect key="frame" x="276" y="11" width="96" height="16"/> + <buttonCell key="cell" type="push" title="Scale To Screen" bezelStyle="rounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="STc-Po-p1X"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="miniSystem"/> + </buttonCell> + <connections> + <action selector="toggleScaleToScreen:" target="-2" id="bbI-nW-6zM"/> + </connections> + </button> + <textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5T3-Vx-mC1"> + <rect key="frame" x="105" y="14" width="48" height="11"/> + <textFieldCell key="cell" controlSize="mini" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Duration:" id="USR-Qf-N5v"> + <font key="font" metaFont="miniSystemBold"/> + <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + <textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vrm-He-CjP"> + <rect key="frame" x="215" y="14" width="21" height="11"/> + <textFieldCell key="cell" controlSize="mini" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="sec" id="ksF-Ma-hB1"> + <font key="font" metaFont="miniSystemBold"/> + <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + <popUpButton toolTip="Select The Length Of Live Preview to Encode" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ASA-X8-16P"> + <rect key="frame" x="158" y="12" width="53" height="15"/> + <popUpButtonCell key="cell" type="push" title="240" bezelStyle="rounded" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" inset="2" selectedItem="XYh-Ls-slf" id="yDS-gf-Y4N"> + <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="miniSystem"/> + <menu key="menu" title="OtherViews" id="KrC-nR-rYk"> + <items> + <menuItem title="240" state="on" id="XYh-Ls-slf"/> + </items> + </menu> + </popUpButtonCell> + <connections> + <action selector="previewDurationPopUpChanged:" target="-2" id="JSm-Eo-Ijw"/> + </connections> + </popUpButton> + <slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wUk-SQ-GhS"> + <rect key="frame" x="18" y="39" width="420" height="17"/> + <sliderCell key="cell" controlSize="mini" continuous="YES" alignment="left" maxValue="9" tickMarkPosition="above" numberOfTickMarks="10" allowsTickMarkValuesOnly="YES" sliderType="linear" id="dPk-jb-w9o"> + <font key="font" metaFont="miniSystem"/> + </sliderCell> + <connections> + <action selector="pictureSliderChanged:" target="-2" id="576-54-fMW"/> + </connections> + </slider> + <textField verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="426" translatesAutoresizingMaskIntoConstraints="NO" id="MkW-KN-I1h"> + <rect key="frame" x="15" y="55" width="430" height="20"/> + <textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" alignment="left" id="DKr-Lp-f2U"> + <font key="font" metaFont="smallSystemBold"/> + <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + <textField verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="422" translatesAutoresizingMaskIntoConstraints="NO" id="7UR-Zz-7iX"> + <rect key="frame" x="15" y="77" width="426" height="12"/> + <textFieldCell key="cell" controlSize="mini" sendsActionOnEndEditing="YES" alignment="left" id="CeL-Pu-NS6"> + <font key="font" metaFont="miniSystemBold"/> + <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + </subviews> + <point key="canvasLocation" x="23" y="360"/> + </customView> + </objects> +</document> diff --git a/macosx/HBPlayer.h b/macosx/HBPlayer.h new file mode 100644 index 000000000..77d6ada6b --- /dev/null +++ b/macosx/HBPlayer.h @@ -0,0 +1,57 @@ +/* HBPlayer.h $ + + 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 <Foundation/Foundation.h> +#import <QuartzCore/QuartzCore.h> + +#import "HBPlayerTrack.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * A protocol for a generic player, + * users should call loadPlayableValueAsynchronouslyWithCompletionHandler + * before calling any other method or property. + */ +@protocol HBPlayer <NSObject> + +- (instancetype)initWithURL:(NSURL *)url; + +@property (nonatomic, readonly) CALayer *layer; + +@property (nonatomic, readonly) NSTimeInterval duration; +@property (nonatomic) NSTimeInterval currentTime; + +@property (nonatomic, readonly, copy) NSArray<HBPlayerTrack *> *audioTracks; +@property (nonatomic, readonly, copy) NSArray<HBPlayerTrack *> *subtitlesTracks; + +- (void)enableAudioTrack:(nullable HBPlayerTrack *)playerTrack; +- (void)enableSubtitlesTrack:(nullable HBPlayerTrack *)playerTrack; + +@property (nonatomic) float rate; +@property (nonatomic) float volume; + +@property (nonatomic, readonly, getter=isPlayable) BOOL playable; + +- (void)loadPlayableValueAsynchronouslyWithCompletionHandler:(nullable void (^)(void))handler; + +- (id)addPeriodicTimeObserverUsingBlock:(void (^)(NSTimeInterval time))block; +- (void)removeTimeObserver:(id)observer; + +- (id)addRateObserverUsingBlock:(void (^)(void))block; +- (void)removeRateObserver:(id)observer; + +- (void)play; +- (void)pause; +- (void)gotoBeginning; +- (void)gotoEnd; +- (void)stepForward; +- (void)stepBackward; + +@end + +NS_ASSUME_NONNULL_END
\ No newline at end of file diff --git a/macosx/HBPlayerHUDController.h b/macosx/HBPlayerHUDController.h new file mode 100644 index 000000000..7becadb64 --- /dev/null +++ b/macosx/HBPlayerHUDController.h @@ -0,0 +1,23 @@ +/* HBPlayerHUDController.h $ + + 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 <Cocoa/Cocoa.h> +#import "HBPlayer.h" +#import "HBHUD.h" + +@protocol HBPlayerHUDControllerDelegate <NSObject> + +- (void)stopPlayer; + +@end + +@interface HBPlayerHUDController : NSViewController <HBHUD> + +@property (nonatomic, nullable, assign) id<HBPlayerHUDControllerDelegate> delegate; + +@property (nonatomic, nullable) id<HBPlayer> player; + +@end diff --git a/macosx/HBPlayerHUDController.m b/macosx/HBPlayerHUDController.m new file mode 100644 index 000000000..03eed5a84 --- /dev/null +++ b/macosx/HBPlayerHUDController.m @@ -0,0 +1,341 @@ +/* HBPlayerHUDController.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 "HBPlayerHUDController.h" + +@interface HBPlayerHUDController () + +@property (weak) IBOutlet NSButton *playButton; +@property (weak) IBOutlet NSSlider *slider; + +@property (weak) IBOutlet NSSlider *volumeSlider; + +@property (weak) IBOutlet NSTextField *currentTimeLabel; +@property (weak) IBOutlet NSTextField *remaingTimeLabel; + +@property (weak) IBOutlet NSPopUpButton *tracksSelection; + +@property (nonatomic, readonly) NSDictionary *monospacedAttr; + +@property (nonatomic, readwrite) id rateObserver; +@property (nonatomic, readwrite) id periodicObserver; + +@end + +@implementation HBPlayerHUDController + +- (NSString *)nibName +{ + return @"HBPlayerHUDController"; +} + +- (void)loadView +{ + [super loadView]; + + if (NSClassFromString(@"NSVisualEffectView") == NO) + { + self.currentTimeLabel.textColor = [NSColor whiteColor]; + self.remaingTimeLabel.textColor = [NSColor whiteColor]; + } + + if ([[NSFont class] respondsToSelector:@selector(monospacedDigitSystemFontOfSize:weight:)]) { + _monospacedAttr = @{NSFontAttributeName: [NSFont monospacedDigitSystemFontOfSize:[NSFont smallSystemFontSize] weight:NSFontWeightRegular]}; + } + else { + _monospacedAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]}; + } +} + +- (BOOL)canBeHidden +{ + return YES; +} + +- (void)setPlayer:(id<HBPlayer>)player +{ + if (_player) + { + [self.player removeRateObserver:self.rateObserver]; + [self.player removeTimeObserver:self.periodicObserver]; + [self _clearTracksMenu]; + } + + _player = player; + + if (player) + { + [self _buildTracksMenu]; + + // 10.7 does not supports weak NSViewController, + // so use self and disable the warning for now. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-retain-cycles" + + self.periodicObserver = [self.player addPeriodicTimeObserverUsingBlock:^(NSTimeInterval time) { + [self _refreshUI]; + }]; + + self.rateObserver = [self.player addRateObserverUsingBlock:^{ + if (self.player.rate != 0.0) + { + self.playButton.image = [NSImage imageNamed:@"PauseTemplate"]; + } + else + { + self.playButton.image = [NSImage imageNamed:@"PlayTemplate"]; + } + }]; + + [self.slider setMinValue:0.0]; + [self.slider setMaxValue:self.player.duration]; + [self.slider setDoubleValue:0.0]; + + [self.player play]; + } +} + +- (void)dealloc +{ + // Remove observers +} + +#pragma mark - Audio and subtitles selection menu + +- (void)_buildTracksMenu +{ + [self _clearTracksMenu]; + + NSArray<HBPlayerTrack *> *audioTracks = self.player.audioTracks; + if (audioTracks.count) + { + [self _addSectionTitle:NSLocalizedString(@"Audio", nil)]; + [self _addTracksItemFromArray:audioTracks selector:@selector(enableAudioTrack:)]; + } + + NSArray<HBPlayerTrack *> *subtitlesTracks = self.player.subtitlesTracks; + if (subtitlesTracks.count) + { + if (audioTracks.count) + { + [self.tracksSelection.menu addItem:[NSMenuItem separatorItem]]; + } + [self _addSectionTitle:NSLocalizedString(@"Subtitles", nil)]; + [self _addTracksItemFromArray:subtitlesTracks selector:@selector(enableSubtitlesTrack:)]; + } +} + +- (void)_clearTracksMenu +{ + for (NSMenuItem *item in [self.tracksSelection.menu.itemArray copy]) + { + if (item.tag != 1) + { + [self.tracksSelection.menu removeItem:item]; + } + } +} + +- (void)_addSectionTitle:(NSString *)title +{ + NSMenuItem *sectionTitle = [[NSMenuItem alloc] init]; + sectionTitle.title = title; + sectionTitle.enabled = NO; + sectionTitle.indentationLevel = 0; + [self.tracksSelection.menu addItem:sectionTitle]; +} + +- (void)_addTracksItemFromArray:(NSArray<HBPlayerTrack *> *)tracks selector:(SEL)selector +{ + for (HBPlayerTrack *track in tracks) + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.title = track.name; + item.enabled = YES; + item.indentationLevel = 1; + item.action = selector; + item.target = self; + item.state = track.enabled; + item.representedObject = track; + [self.tracksSelection.menu addItem:item]; + } +} + +- (void)_updateTracksMenuState +{ + for (NSMenuItem *item in self.tracksSelection.menu.itemArray) + { + if (item.representedObject) + { + HBPlayerTrack *track = (HBPlayerTrack *)item.representedObject; + item.state = track.enabled; + } + } +} + +- (IBAction)enableAudioTrack:(NSMenuItem *)sender +{ + [self.player enableAudioTrack:sender.representedObject]; + [self _updateTracksMenuState]; +} + +- (IBAction)enableSubtitlesTrack:(NSMenuItem *)sender +{ + [self.player enableSubtitlesTrack:sender.representedObject]; + [self _updateTracksMenuState]; +} + +- (NSString *)_timeToTimecode:(NSTimeInterval)timeInSeconds +{ + UInt16 seconds = (UInt16)fmod(timeInSeconds, 60.0); + UInt16 minutes = (UInt16)fmod(timeInSeconds / 60.0, 60.0); + UInt16 milliseconds = (UInt16)((timeInSeconds - (int) timeInSeconds) * 1000); + + return [NSString stringWithFormat:@"%02d:%02d.%03d", minutes, seconds, milliseconds]; +} + +- (NSAttributedString *)_monospacedString:(NSString *)string +{ + return [[NSAttributedString alloc] initWithString:string attributes:self.monospacedAttr]; + +} + +- (void)_refreshUI +{ + if (self.player) + { + NSTimeInterval currentTime = self.player.currentTime; + NSTimeInterval duration = self.player.duration; + + self.slider.doubleValue = currentTime; + self.currentTimeLabel.attributedStringValue = [self _monospacedString:[self _timeToTimecode:currentTime]]; + self.remaingTimeLabel.attributedStringValue = [self _monospacedString:[self _timeToTimecode:duration - currentTime]]; + } +} + +- (IBAction)playPauseToggle:(id)sender +{ + if (self.player.rate != 0.0) + { + [self.player pause]; + } + else + { + [self.player play]; + } +} + +- (IBAction)goToBeginning:(id)sender +{ + [self.player gotoBeginning]; +} + +- (IBAction)goToEnd:(id)sender +{ + [self.player gotoEnd]; +} + +- (IBAction)showPicturesPreview:(id)sender +{ + [self.delegate stopPlayer]; +} + +- (IBAction)sliderChanged:(NSSlider *)sender +{ + self.player.currentTime = sender.doubleValue; +} + +- (IBAction)maxVolume:(id)sender +{ + self.volumeSlider.doubleValue = 1; + self.player.volume = 1; +} + +- (IBAction)mute:(id)sender +{ + self.volumeSlider.doubleValue = 0; + self.player.volume = 0; +} + +- (IBAction)volumeSliderChanged:(NSSlider *)sender +{ + self.player.volume = sender.floatValue; +} + +#pragma mark - Keyboard and mouse wheel control + +- (BOOL)HB_keyDown:(NSEvent *)event +{ + unichar key = [event.charactersIgnoringModifiers characterAtIndex:0]; + + if (self.player) + { + if (key == 32) + { + if (self.player.rate != 0.0) + [self.player pause]; + else + [self.player play]; + } + else if (key == 'k') + [self.player pause]; + else if (key == 'l') + { + float rate = self.player.rate; + rate += 1.0f; + [self.player play]; + self.player.rate = rate; + } + else if (key == 'j') + { + float rate = self.player.rate; + rate -= 1.0f; + [self.player play]; + self.player.rate = rate; + } + else if (event.modifierFlags & NSAlternateKeyMask && key == NSLeftArrowFunctionKey) + { + [self.player gotoBeginning]; + } + else if (event.modifierFlags & NSAlternateKeyMask && key == NSRightArrowFunctionKey) + { + [self.player gotoEnd]; + } + else if (key == NSLeftArrowFunctionKey) + { + [self.player stepBackward]; + } + else if (key == NSRightArrowFunctionKey) + { + [self.player stepForward]; + } + else + { + return NO; + } + } + else + { + return NO; + } + + return YES; +} + +- (BOOL)HB_scrollWheel:(NSEvent *)theEvent; +{ + if (theEvent.deltaY < 0) + { + self.player.currentTime = self.player.currentTime + 0.5; + } + else if (theEvent.deltaY > 0) + { + self.player.currentTime = self.player.currentTime - 0.5; + } + return YES; +} + +@end diff --git a/macosx/HBPlayerHUDController.xib b/macosx/HBPlayerHUDController.xib new file mode 100644 index 000000000..812c83c90 --- /dev/null +++ b/macosx/HBPlayerHUDController.xib @@ -0,0 +1,152 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15F28b" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> + <dependencies> + <deployment identifier="macosx"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="HBPlayerHUDController"> + <connections> + <outlet property="currentTimeLabel" destination="uYx-2C-8et" id="8fD-0Q-308"/> + <outlet property="playButton" destination="mLA-ei-4sK" id="Eag-8h-xCG"/> + <outlet property="remaingTimeLabel" destination="PVD-pq-1ZG" id="CFb-is-Tcd"/> + <outlet property="slider" destination="lBp-JT-ehn" id="cNv-P0-agY"/> + <outlet property="tracksSelection" destination="Vj1-Za-AUV" id="8Se-0H-ILD"/> + <outlet property="view" destination="xT7-Bx-eCV" id="GXR-ZH-hT6"/> + <outlet property="volumeSlider" destination="gzA-no-jdv" id="Yba-8g-L1Z"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <customView hidden="YES" id="xT7-Bx-eCV" userLabel="Playback Controls" customClass="HBHUDView"> + <rect key="frame" x="0.0" y="0.0" width="460" height="100"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> + <subviews> + <slider verticalHuggingPriority="750" id="lBp-JT-ehn"> + <rect key="frame" x="69" y="18" width="322" height="17"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> + <sliderCell key="cell" controlSize="small" continuous="YES" alignment="left" maxValue="100" tickMarkPosition="above" sliderType="linear" id="mMk-re-3u5"/> + <connections> + <action selector="sliderChanged:" target="-2" id="YM4-mh-fr5"/> + </connections> + </slider> + <button toolTip="Toggle Play/Pause" id="mLA-ei-4sK"> + <rect key="frame" x="212" y="46" width="36" height="36"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="PlayTemplate" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" id="jG9-2O-OES"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="playPauseToggle:" target="-2" id="LLB-0k-P6K"/> + </connections> + </button> + <button toolTip="Go To Beginning" id="WuP-9l-8AN"> + <rect key="frame" x="170" y="51" width="32" height="26"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="PrevTemplate" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" id="IBZ-MN-bJO"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="goToBeginning:" target="-2" id="m24-WZ-vGD"/> + </connections> + </button> + <button toolTip="Go To End" id="CQ0-pk-SFk"> + <rect key="frame" x="258" y="51" width="32" height="26"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NextTemplate" imagePosition="only" alignment="center" controlSize="mini" imageScaling="proportionallyDown" id="LUb-dY-8hG"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="miniSystem"/> + </buttonCell> + <connections> + <action selector="goToEnd:" target="-2" id="Rp0-53-4gw"/> + </connections> + </button> + <textField verticalHuggingPriority="750" id="PVD-pq-1ZG"> + <rect key="frame" x="395" y="20" width="55" height="14"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + <textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" alignment="right" title="00:00:00" id="6A0-9h-5UY"> + <font key="font" metaFont="smallSystem"/> + <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + <textField verticalHuggingPriority="750" id="uYx-2C-8et"> + <rect key="frame" x="10" y="20" width="55" height="14"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" alignment="left" title="00:00:00" id="dGi-LK-P1E"> + <font key="font" metaFont="smallSystem"/> + <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + <slider verticalHuggingPriority="750" id="gzA-no-jdv"> + <rect key="frame" x="34" y="55" width="65" height="17"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <sliderCell key="cell" controlSize="small" continuous="YES" state="on" alignment="left" maxValue="1" doubleValue="1" tickMarkPosition="above" sliderType="linear" id="aQz-j6-bQi"/> + <connections> + <action selector="volumeSliderChanged:" target="-2" id="rLr-Ug-i8q"/> + </connections> + </slider> + <button toolTip="Return to still previews" id="84p-08-fGK" userLabel="Stop Button"> + <rect key="frame" x="424" y="55" width="16" height="16"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NSStopProgressFreestandingTemplate" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" id="hWC-BU-ynp"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="showPicturesPreview:" target="-2" id="kCs-UI-Epb"/> + </connections> + </button> + <button toolTip="Go To Beginning" id="euL-Tg-dmT"> + <rect key="frame" x="12" y="55" width="18" height="18"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="volLowTemplate" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" id="njB-22-thn"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="mute:" target="-2" id="CIm-ce-Lnf"/> + </connections> + </button> + <button id="Kiv-cL-wYI"> + <rect key="frame" x="103" y="55" width="18" height="18"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="volHighTemplate" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" id="rgx-mB-Rva"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="maxVolume:" target="-2" id="89B-BN-U8S"/> + </connections> + </button> + <popUpButton id="Vj1-Za-AUV"> + <rect key="frame" x="336" y="47" width="73" height="30"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> + <popUpButtonCell key="cell" type="push" title="Tracks" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" state="on" borderStyle="border" tag="1" imageScaling="proportionallyDown" inset="2" pullsDown="YES" autoenablesItems="NO" altersStateOfSelectedItem="NO" id="uwd-kr-YUM"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="smallSystem"/> + <menu key="menu" autoenablesItems="NO" id="J1o-jV-bDZ"> + <items> + <menuItem title="Tracks" state="on" tag="1" hidden="YES" id="6BQ-wU-p2C"> + <modifierMask key="keyEquivalentModifierMask" shift="YES"/> + </menuItem> + </items> + </menu> + </popUpButtonCell> + </popUpButton> + </subviews> + <point key="canvasLocation" x="466" y="429"/> + </customView> + </objects> + <resources> + <image name="NSStopProgressFreestandingTemplate" width="14" height="14"/> + <image name="NextTemplate" width="48" height="48"/> + <image name="PlayTemplate" width="48" height="48"/> + <image name="PrevTemplate" width="48" height="48"/> + <image name="volHighTemplate" width="48" height="48"/> + <image name="volLowTemplate" width="48" height="48"/> + </resources> +</document> diff --git a/macosx/HBPlayerTrack.h b/macosx/HBPlayerTrack.h new file mode 100644 index 000000000..950fdfdda --- /dev/null +++ b/macosx/HBPlayerTrack.h @@ -0,0 +1,21 @@ +/* HBPlayerTrack.h $ + + 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 <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +@interface HBPlayerTrack : NSObject + +- (instancetype)initWithTrackName:(NSString *)name object:(id)representedObject enabled:(BOOL)enabled; + +@property (nonatomic, readonly) NSString *name; +@property (nonatomic, readwrite) BOOL enabled; +@property (nonatomic, readonly) id representedObject; + +@end + +NS_ASSUME_NONNULL_END
\ No newline at end of file diff --git a/macosx/HBPlayerTrack.m b/macosx/HBPlayerTrack.m new file mode 100644 index 000000000..1be7bc6d5 --- /dev/null +++ b/macosx/HBPlayerTrack.m @@ -0,0 +1,22 @@ +/* HBPlayerTrack.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 "HBPlayerTrack.h" + +@implementation HBPlayerTrack + +- (instancetype)initWithTrackName:(NSString *)name object:(id)representedObject enabled:(BOOL)enabled +{ + self = [super init]; + if (self) { + _name = name; + _enabled = enabled; + _representedObject = representedObject; + } + return self; +} + +@end diff --git a/macosx/HBPreviewController.m b/macosx/HBPreviewController.m index 4e7d259e2..1db48cdcd 100644 --- a/macosx/HBPreviewController.m +++ b/macosx/HBPreviewController.m @@ -1,4 +1,4 @@ -/* $Id: HBPreviewController.mm,v 1.11 2005/08/01 15:10:44 titer Exp $ +/* $Id: HBPreviewController.m $ This file is part of the HandBrake source code. Homepage: <http://handbrake.fr/>. @@ -8,65 +8,42 @@ #import "HBPreviewGenerator.h" #import "HBPictureController.h" +#import "HBController.h" #import "HBPreviewView.h" -#import "HBController.h" +#import "HBPlayer.h" +#import "HBQTKitPlayer.h" +#import "HBAVPlayer.h" -#import <QTKit/QTKit.h> -#import "QTKit+HBQTMovieExtensions.h" +#import "HBPictureHUDController.h" +#import "HBEncodingProgressHUDController.h" +#import "HBPlayerHUDController.h" + +#import "NSWindow+HBAdditions.h" #define ANIMATION_DUR 0.15 +#define HUD_FADEOUT_TIME 4.0 -// make min width and height of preview window large enough for hud +// Make min width and height of preview window large enough for hud. #define MIN_WIDTH 480.0 #define MIN_HEIGHT 360.0 -typedef enum ViewMode : NSUInteger { - ViewModePicturePreview, - ViewModeEncoding, - ViewModeMoviePreview -} ViewMode; +@interface HBPreviewController () <HBPreviewGeneratorDelegate, HBPictureHUDControllerDelegate, HBEncodingProgressHUDControllerDelegate, HBPlayerHUDControllerDelegate> -@interface HBPreviewController () <HBPreviewGeneratorDelegate> -{ - /* HUD boxes */ - IBOutlet NSView * fPictureControlBox; - IBOutlet NSView * fEncodingControlBox; - IBOutlet NSView * fMoviePlaybackControlBox; +@property (nonatomic, readonly) HBPictureHUDController *pictureHUD; +@property (nonatomic, readonly) HBEncodingProgressHUDController *encodingHUD; +@property (nonatomic, readonly) HBPlayerHUDController *playerHUD; - IBOutlet NSSlider * fPictureSlider; - IBOutlet NSTextField * fInfoField; - IBOutlet NSTextField * fscaleInfoField; +@property (nonatomic, readwrite) NSViewController<HBHUD> *currentHUD; - /* Full Screen Mode Toggle */ - IBOutlet NSButton * fScaleToScreenToggleButton; +@property (nonatomic) NSTimer *hudTimer; +@property (nonatomic) BOOL mouseInWindow; - /* Movie Previews */ - IBOutlet QTMovieView * fMovieView; - /* Playback Panel Controls */ - IBOutlet NSButton * fPlayPauseButton; - IBOutlet NSSlider * fMovieScrubberSlider; - IBOutlet NSTextField * fMovieInfoField; - - IBOutlet NSProgressIndicator * fMovieCreationProgressIndicator; - IBOutlet NSTextField * fPreviewMovieStatusField; - - /* Popup of choices for length of preview in seconds */ - IBOutlet NSPopUpButton * fPreviewMovieLengthPopUp; -} +@property (nonatomic) id<HBPlayer> player; -@property (nonatomic, readwrite) HBPictureController *pictureSettingsWindow; +@property (nonatomic) HBPictureController *pictureSettingsWindow; -@property (nonatomic) ViewMode currentViewMode; @property (nonatomic) NSPoint windowCenterPoint; - -@property (nonatomic, strong) NSTimer *hudTimer; - -@property (nonatomic) NSUInteger pictureIndex; - -@property (nonatomic, strong) QTMovie *movie; -@property (nonatomic, strong) NSTimer *movieTimer; - @property (weak) IBOutlet HBPreviewView *previewView; @end @@ -92,51 +69,48 @@ typedef enum ViewMode : NSUInteger { { NSPoint center = NSPointFromString(centerString); self.windowCenterPoint = center; - [self resizeWindowForViewSize:NSMakeSize(MIN_WIDTH, MIN_HEIGHT) animate:NO]; + [self.window HB_resizeToBestSizeForViewSize:NSMakeSize(MIN_WIDTH, MIN_HEIGHT) center:self.windowCenterPoint animate:NO]; } else { - self.windowCenterPoint = [self centerPoint]; + self.windowCenterPoint = [self.window HB_centerPoint]; } self.window.excludedFromWindowsMenu = YES; self.window.acceptsMouseMovedEvents = YES; - // we set the preview length popup in seconds - [fPreviewMovieLengthPopUp removeAllItems]; - [fPreviewMovieLengthPopUp addItemsWithTitles:@[@"15", @"30", @"45", @"60", @"90", - @"120", @"150", @"180", @"210", @"240"]]; + _pictureHUD = [[HBPictureHUDController alloc] init]; + self.pictureHUD.delegate = self; + _encodingHUD = [[HBEncodingProgressHUDController alloc] init]; + self.encodingHUD.delegate = self; + _playerHUD = [[HBPlayerHUDController alloc] init]; + self.playerHUD.delegate = self; - if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]) - { - [fPreviewMovieLengthPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] - objectForKey:@"PreviewLength"]]; - } - if (!fPreviewMovieLengthPopUp.selectedItem) - { - // currently hard set default to 15 seconds - [fPreviewMovieLengthPopUp selectItemAtIndex: 0]; - } + [self.window.contentView addSubview:self.pictureHUD.view]; + [self.window.contentView addSubview:self.encodingHUD.view]; + [self.window.contentView addSubview:self.playerHUD.view]; // Relocate our hud origins. - NSPoint hudControlBoxOrigin = fMoviePlaybackControlBox.frame.origin; - fPictureControlBox.frameOrigin = hudControlBoxOrigin; - fEncodingControlBox.frameOrigin = hudControlBoxOrigin; - fMoviePlaybackControlBox.frameOrigin = hudControlBoxOrigin; + CGPoint origin = CGPointMake(floor((MIN_WIDTH - _pictureHUD.view.bounds.size.width) / 2), MIN_HEIGHT / 10); + + [self.pictureHUD.view setFrameOrigin:origin]; + [self.encodingHUD.view setFrameOrigin:origin]; + [self.playerHUD.view setFrameOrigin:origin]; - [self hideHud]; + self.currentHUD = self.pictureHUD; + self.currentHUD.view.hidden = YES; - fMovieView.hidden = YES; - fMovieView.delegate = self; - [fMovieView setControllerVisible:NO]; + NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:self.window.contentView.frame + options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingInVisibleRect | NSTrackingActiveAlways + owner:self + userInfo:nil]; + [self.window.contentView addTrackingArea:trackingArea]; } - (void)dealloc { - [self removeMovieObservers]; - [_hudTimer invalidate]; - [_movieTimer invalidate]; + _generator.delegate = nil; [_generator cancel]; } @@ -188,21 +162,13 @@ typedef enum ViewMode : NSUInteger { generator.delegate = self; // adjust the preview slider length - [fPictureSlider setMaxValue: generator.imagesCount - 1.0]; - [fPictureSlider setNumberOfTickMarks: generator.imagesCount]; - - if (self.pictureIndex > generator.imagesCount) - { - self.pictureIndex = generator.imagesCount - 1; - } - - [self switchViewToMode:ViewModePicturePreview]; - [self displayPreviewAtIndex:self.pictureIndex]; + self.pictureHUD.pictureCount = generator.imagesCount; + [self switchStateToHUD:self.pictureHUD]; } else { self.previewView.image = nil; - [self hideHud]; + self.currentHUD.view.hidden = YES; self.window.title = NSLocalizedString(@"Preview", nil); } } @@ -212,8 +178,7 @@ typedef enum ViewMode : NSUInteger { if (self.generator) { [self.generator cancel]; - [self switchViewToMode:ViewModePicturePreview]; - [self displayPreviewAtIndex:self.pictureIndex]; + [self switchStateToHUD:self.pictureHUD]; } } @@ -221,11 +186,7 @@ typedef enum ViewMode : NSUInteger { { [super showWindow:sender]; - if (self.currentViewMode == ViewModeMoviePreview) - { - [self startMovieTimer]; - } - else if (self.currentViewMode == ViewModePicturePreview) + if (self.currentHUD == self.pictureHUD) { [self reloadPreviews]; } @@ -233,14 +194,13 @@ typedef enum ViewMode : NSUInteger { - (void)windowWillClose:(NSNotification *)aNotification { - if (self.currentViewMode == ViewModeEncoding) + if (self.currentHUD == self.encodingHUD) { - [self cancelCreateMoviePreview:self]; + [self cancelEncoding]; } - else if (self.currentViewMode == ViewModeMoviePreview) + else if (self.currentHUD == self.playerHUD) { - [fMovieView pause:self]; - [self stopMovieTimer]; + [self.player pause]; } [self.pictureSettingsWindow close]; @@ -249,11 +209,10 @@ typedef enum ViewMode : NSUInteger { - (void)windowDidChangeBackingProperties:(NSNotification *)notification { - NSWindow *theWindow = (NSWindow *)[notification object]; + NSWindow *theWindow = (NSWindow *)notification.object; - CGFloat newBackingScaleFactor = [theWindow backingScaleFactor]; - CGFloat oldBackingScaleFactor = [[notification userInfo][@"NSBackingPropertyOldScaleFactorKey"] - doubleValue]; + CGFloat newBackingScaleFactor = theWindow.backingScaleFactor; + CGFloat oldBackingScaleFactor = [notification.userInfo[@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; if (newBackingScaleFactor != oldBackingScaleFactor) { @@ -268,20 +227,11 @@ typedef enum ViewMode : NSUInteger { #pragma mark - Window sizing -/** - * Calculates and returns the center point of the window - */ -- (NSPoint)centerPoint { - NSPoint center = NSMakePoint(floor(self.window.frame.origin.x + self.window.frame.size.width / 2), - floor(self.window.frame.origin.y + self.window.frame.size.height / 2)); - return center; -} - - (void)windowDidMove:(NSNotification *)notification { if (self.previewView.fitToView == NO) { - self.windowCenterPoint = [self centerPoint]; + self.windowCenterPoint = [self.window HB_centerPoint]; [[NSUserDefaults standardUserDefaults] setObject:NSStringFromPoint(self.windowCenterPoint) forKey:@"HBPreviewWindowCenter"]; } } @@ -291,68 +241,6 @@ typedef enum ViewMode : NSUInteger { [self updateSizeLabels]; } -/** - * Resizes the entire window to accomodate a view of a particular size. - */ -- (void)resizeWindowForViewSize:(NSSize)viewSize animate:(BOOL)performAnimation -{ - NSWindow *window = self.window; - NSSize currentSize = [window.contentView frame].size; - NSRect frame = window.frame; - - // Calculate border around content region of the frame - int borderX = (int)(frame.size.width - currentSize.width); - int borderY = (int)(frame.size.height - currentSize.height); - - // Make sure the frame is smaller than the screen - NSSize maxSize = window.screen.visibleFrame.size; - - // if we are not Scale To Screen, put an 10% of visible screen on the window - maxSize.width = maxSize.width * 0.90; - maxSize.height = maxSize.height * 0.90; - - // Set the new frame size - // Add the border to the new frame size so that the content region - // of the frame is large enough to accomodate the preview image - frame.size.width = viewSize.width + borderX; - frame.size.height = viewSize.height + borderY; - - // compare frame to max size of screen - if (frame.size.width > maxSize.width) - { - frame.size.width = maxSize.width; - } - if (frame.size.height > maxSize.height) - { - frame.size.height = maxSize.height; - } - - // Since upon launch we can open up the preview window if it was open - // the last time we quit (and at the size it was) we want to make - // sure that upon resize we do not have the window off the screen - // So check the origin against the screen origin and adjust if - // necessary. - NSSize screenSize = window.screen.visibleFrame.size; - NSPoint screenOrigin = window.screen.visibleFrame.origin; - - frame.origin.x = self.windowCenterPoint.x - floor(frame.size.width / 2); - frame.origin.y = self.windowCenterPoint.y - floor(frame.size.height / 2); - - // our origin is off the screen to the left - if (frame.origin.x < screenOrigin.x) - { - // so shift our origin to the right - frame.origin.x = screenOrigin.x; - } - else if ((frame.origin.x + frame.size.width) > (screenOrigin.x + screenSize.width)) - { - // the right side of the preview is off the screen, so shift to the left - frame.origin.x = (screenOrigin.x + screenSize.width) - frame.size.width; - } - - [window setFrame:frame display:YES animate:performAnimation]; -} - - (void)updateSizeLabels { if (self.generator) @@ -375,8 +263,8 @@ typedef enum ViewMode : NSUInteger { } // Set the info fields in the hud controller - fInfoField.stringValue = self.generator.info; - fscaleInfoField.stringValue = scaleString; + self.pictureHUD.info = self.generator.info; + self.pictureHUD.scale = scaleString; // Set the info field in the window title bar self.window.title = [NSString stringWithFormat:NSLocalizedString(@"Preview - %@ %@", nil), @@ -384,138 +272,86 @@ typedef enum ViewMode : NSUInteger { } } -#pragma mark - Hud mode +#pragma mark - Hud State /** - * Enable/Disable an arbitrary number of UI elements. - * @param boxes an array of UI elements - * @param indexes a set of indexes of the elements in boxes to be enabled - */ -- (void) toggleBoxes: (NSArray *) boxes usingIndexes: (NSIndexSet *) indexes -{ - [NSAnimationContext beginGrouping]; - [[NSAnimationContext currentContext] setDuration:ANIMATION_DUR]; - - [boxes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - BOOL hide = [indexes containsIndex:idx] ? NO : YES; - if (hide) - { - [self hideHudWithAnimation:obj]; - } - else - { - [self showHudWithAnimation:obj]; - } - }]; - - [NSAnimationContext endGrouping]; -} - -/** - * Switch the preview controller to one of his view mode: - * ViewModePicturePreview, ViewModeEncoding, ViewModeMoviePreview + * Switch the preview controller to one of his hud mode: * This methods is the only way to change the mode, do not try otherwise. - * @param mode ViewMode mode + * @param hud NSViewController<HBHUD> the hud to show */ -- (void) switchViewToMode: (ViewMode) mode +- (void)switchStateToHUD:(NSViewController<HBHUD> *)hud { - switch (mode) { - case ViewModePicturePreview: - { - if (self.currentViewMode == ViewModeEncoding) - { - [self toggleBoxes:@[fPictureControlBox, fEncodingControlBox] - usingIndexes:[NSIndexSet indexSetWithIndex:0]]; - [fMovieCreationProgressIndicator stopAnimation:self]; - } - else if (self.currentViewMode == ViewModeMoviePreview) - { - // Stop playback and remove the observers - [fMovieView pause:self]; - [self stopMovieTimer]; - [self removeMovieObservers]; - - [self toggleBoxes:@[fPictureControlBox, fMoviePlaybackControlBox, fMovieView] - usingIndexes:[NSIndexSet indexSetWithIndex:0]]; - - /* Release the movie */ - [fMovieView setMovie:nil]; - self.movie = nil; - } - - break; - } + if (self.currentHUD == self.playerHUD) + { + [self exitPlayerState]; + } - case ViewModeEncoding: - { - [fMovieCreationProgressIndicator setDoubleValue:0]; - [fMovieCreationProgressIndicator startAnimation:self]; - [self toggleBoxes:@[fEncodingControlBox, fPictureControlBox, fMoviePlaybackControlBox] - usingIndexes:[NSIndexSet indexSetWithIndex:0]]; + if (hud == self.pictureHUD) + { + [self enterPictureState]; + } + else if (hud == self.encodingHUD) + { + [self enterEncodingState]; + } + else if (hud == self.playerHUD) + { + [self enterPlayerState]; + } - break; - } + // Show the current hud + NSMutableArray *huds = [@[self.pictureHUD, self.encodingHUD, self.playerHUD] mutableCopy]; + [huds removeObject:hud]; + for (NSViewController *controller in huds) { + controller.view.hidden = YES; + } + hud.view.hidden = NO; + hud.view.layer.opacity = 1.0; - case ViewModeMoviePreview: - { - [self toggleBoxes:@[fMovieView, fMoviePlaybackControlBox, fEncodingControlBox, fPictureControlBox] - usingIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)]]; + [self.window makeFirstResponder:hud.view]; + [self startHudTimer]; + self.currentHUD = hud; +} - [fMovieCreationProgressIndicator stopAnimation:self]; - [self initPreviewScrubberForMovie]; - [self startMovieTimer]; +#pragma mark - HUD Control Overlay - // Install movie notifications - [self addMovieObservers]; - } - break; +- (void)mouseEntered:(NSEvent *)theEvent +{ + if (self.generator) + { + NSView *hud = self.currentHUD.view; - default: - break; + [self showHudWithAnimation:hud]; + [self startHudTimer]; } - - self.currentViewMode = mode; + self.mouseInWindow = YES; } -#pragma mark - -#pragma mark Hud Control Overlay +- (void)mouseExited:(NSEvent *)theEvent +{ + [self hudTimerFired:nil]; + self.mouseInWindow = NO; +} - (void)mouseMoved:(NSEvent *)theEvent { [super mouseMoved:theEvent]; - NSPoint mouseLoc = [theEvent locationInWindow]; - /* Test for mouse location to show/hide hud controls */ - if (self.currentViewMode != ViewModeEncoding && self.generator) + // Test for mouse location to show/hide hud controls + if (self.generator && self.mouseInWindow) { - /* Since we are not encoding, verify which control hud to show - * or hide based on aMovie ( aMovie indicates we need movie controls ) - */ - NSView *hud; - if (self.currentViewMode == !ViewModeMoviePreview) // No movie loaded up - { - hud = fPictureControlBox; - } - else // We have a movie - { - hud = fMoviePlaybackControlBox; - } + NSView *hud = self.currentHUD.view; + NSPoint mouseLoc = theEvent.locationInWindow; - if (NSPointInRect(mouseLoc, [hud frame])) + if (NSPointInRect(mouseLoc, hud.frame)) { - [self showHudWithAnimation:hud]; [self stopHudTimer]; } - else if (NSPointInRect(mouseLoc, [[[self window] contentView] frame])) + else { [self showHudWithAnimation:hud]; [self startHudTimer]; } - else - { - [self hideHudWithAnimation:hud]; - [self stopHudTimer]; - } } } @@ -524,13 +360,13 @@ typedef enum ViewMode : NSUInteger { // The standard view animator doesn't play // nicely with the Yosemite visual effects yet. // So let's do the fade ourself. - if (hud.layer.opacity == 0 || [hud isHidden]) + if (hud.layer.opacity == 0 || hud.isHidden) { [hud setHidden:NO]; [CATransaction begin]; CABasicAnimation *fadeInAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; - fadeInAnimation.fromValue = @(0.0); + fadeInAnimation.fromValue = @([hud.layer.presentationLayer opacity]); fadeInAnimation.toValue = @(1.0); fadeInAnimation.beginTime = 0.0; fadeInAnimation.duration = ANIMATION_DUR; @@ -547,41 +383,28 @@ typedef enum ViewMode : NSUInteger { if (hud.layer.opacity != 0) { [CATransaction begin]; - [CATransaction setCompletionBlock:^{ - if (hud.layer.opacity == 0) - { - [hud setHidden:YES]; - } - }]; - CABasicAnimation *fadeInAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; - fadeInAnimation.fromValue = @([hud.layer.presentationLayer opacity]); - fadeInAnimation.toValue = @(0.0); - fadeInAnimation.beginTime = 0.0; - fadeInAnimation.duration = ANIMATION_DUR; + CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; + fadeOutAnimation.fromValue = @([hud.layer.presentationLayer opacity]); + fadeOutAnimation.toValue = @(0.0); + fadeOutAnimation.beginTime = 0.0; + fadeOutAnimation.duration = ANIMATION_DUR; - [hud.layer addAnimation:fadeInAnimation forKey:nil]; + [hud.layer addAnimation:fadeOutAnimation forKey:nil]; [hud.layer setOpacity:0]; [CATransaction commit]; } } -- (void)hideHud -{ - [fPictureControlBox setHidden:YES]; - [fMoviePlaybackControlBox setHidden:YES]; - [fEncodingControlBox setHidden:YES]; -} - - (void)startHudTimer { if (self.hudTimer) { - [self.hudTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:8.0]]; + [self.hudTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:HUD_FADEOUT_TIME]]; } else { - self.hudTimer = [NSTimer scheduledTimerWithTimeInterval:8.0 target:self selector:@selector(hudTimerFired:) + self.hudTimer = [NSTimer scheduledTimerWithTimeInterval:HUD_FADEOUT_TIME target:self selector:@selector(hudTimerFired:) userInfo:nil repeats:YES]; } } @@ -592,18 +415,21 @@ typedef enum ViewMode : NSUInteger { self.hudTimer = nil; } -- (void)hudTimerFired: (NSTimer *)theTimer +- (void)hudTimerFired:(NSTimer *)theTimer { - // Regardless which control box is active, after the timer - // period we want either one to fade to hidden. - [self hideHudWithAnimation:fPictureControlBox]; - [self hideHudWithAnimation:fMoviePlaybackControlBox]; - + if (self.currentHUD.canBeHidden) + { + [self hideHudWithAnimation:self.currentHUD.view]; + } [self stopHudTimer]; } -#pragma mark - -#pragma mark Still previews mode +#pragma mark - Still previews mode + +- (void)enterPictureState +{ + [self displayPreviewAtIndex:self.pictureHUD.selectedIndex]; +} /** * Adjusts the window to draw the current picture (fPicture) adjusting its size as @@ -611,6 +437,11 @@ typedef enum ViewMode : NSUInteger { */ - (void)displayPreviewAtIndex:(NSUInteger)idx { + if (!self.generator) + { + return; + } + if (self.window.isVisible) { CGImageRef fPreviewImage = [self.generator copyImageAtIndex:idx shouldCache:YES]; @@ -625,33 +456,18 @@ typedef enum ViewMode : NSUInteger { // Scale the window to the image size NSSize windowSize = [self.previewView optimalViewSizeForImageSize:imageScaledSize minSize:NSMakeSize(MIN_WIDTH, MIN_HEIGHT)]; - [self resizeWindowForViewSize:windowSize animate:self.window.isVisible]; + [self.window HB_resizeToBestSizeForViewSize:windowSize center:self.windowCenterPoint animate:self.window.isVisible]; } [self updateSizeLabels]; } -- (IBAction)previewDurationPopUpChanged:(id)sender -{ - [[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"]; -} - -- (IBAction)pictureSliderChanged:(id)sender -{ - if ((self.pictureIndex != [fPictureSlider intValue] || !sender) && self.generator) { - self.pictureIndex = [fPictureSlider intValue]; - [self displayPreviewAtIndex:self.pictureIndex]; - } -} - -- (IBAction)toggleScaleToScreen:(id)sender +- (void)toggleScaleToScreen { if (self.previewView.fitToView == YES) { self.previewView.fitToView = NO; - fScaleToScreenToggleButton.title = NSLocalizedString(@"Scale To Screen", nil); - - [self displayPreviewAtIndex:self.pictureIndex]; + [self displayPreviewAtIndex:self.pictureHUD.selectedIndex]; } else { @@ -660,11 +476,10 @@ typedef enum ViewMode : NSUInteger { { [self.window setFrame:self.window.screen.visibleFrame display:YES animate:YES]; } - fScaleToScreenToggleButton.title = NSLocalizedString(@"Actual Scale", nil); } } -- (IBAction) showPictureSettings: (id) sender +- (void)showPictureSettings { if (self.pictureSettingsWindow == nil) { @@ -676,299 +491,137 @@ typedef enum ViewMode : NSUInteger { [self.pictureSettingsWindow showWindow:self]; } -#pragma mark - -#pragma mark Movie preview mode - -- (void) updateProgress: (double) progress info: (NSString *) progressInfo { - [fPreviewMovieStatusField setStringValue: progressInfo]; +#pragma mark - Encoding mode - [fMovieCreationProgressIndicator setIndeterminate: NO]; - [fMovieCreationProgressIndicator setDoubleValue: progress]; -} - -- (void)didCancelMovieCreation -{ - [self switchViewToMode:ViewModePicturePreview]; -} - -- (void) didCreateMovieAtURL: (NSURL *) fileURL +- (void)enterEncodingState { - /* Load the new movie into fMovieView */ - if (fileURL) - { - NSError *outError; - NSDictionary *movieAttributes = @{QTMovieURLAttribute: fileURL, - QTMovieAskUnresolvedDataRefsAttribute: @NO, - @"QTMovieOpenForPlaybackAttribute": @YES, - @"QTMovieOpenAsyncRequiredAttribute": @NO, - @"QTMovieOpenAsyncOKAttribute": @NO, - @"QTMovieIsSteppableAttribute": @YES, - QTMovieApertureModeAttribute: QTMovieApertureModeClean}; - - QTMovie *movie = [[QTMovie alloc] initWithAttributes:movieAttributes error:&outError]; - - if (!movie) - { - NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"HandBrake can't open the preview.", nil) - defaultButton:NSLocalizedString(@"Open in external player", nil) - alternateButton:NSLocalizedString(@"Cancel", nil) - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"HandBrake can't playback this combination of video/audio/container format. Do you want to open it in an external player?", nil)]; - [alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) { - if (returnCode == NSModalResponseOK) - { - [[NSWorkspace sharedWorkspace] openURL:fileURL]; - } - }]; - [self switchViewToMode:ViewModePicturePreview]; - } - else - { - // Scale the fMovieView to the picture player size - [fMovieView setFrame:self.previewView.pictureFrame]; - - [fMovieView setMovie:movie]; - [movie setDelegate:self]; - - // get and enable subtitles - NSArray *subtitlesArray = [movie tracksOfMediaType:QTMediaTypeSubtitle]; - if (subtitlesArray.count) - { - // enable the first tx3g subtitle track - [subtitlesArray[0] setEnabled:YES]; - } - else - { - // Perian subtitles - subtitlesArray = [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]; - } - } - - // to actually play the movie - self.movie = movie; - - [self switchViewToMode:ViewModeMoviePreview]; - - [fMovieView play:movie]; - } - } + self.encodingHUD.progress = 0; } -- (IBAction) cancelCreateMoviePreview: (id) sender +- (void)cancelEncoding { [self.generator cancel]; } -- (IBAction) createMoviePreview: (id) sender +- (void)createMoviePreviewWithPictureIndex:(NSUInteger)index duration:(NSUInteger)duration { - if (!self.generator) - return; - - if ([self.generator createMovieAsyncWithImageAtIndex:self.pictureIndex - duration:[[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue]]) + if ([self.generator createMovieAsyncWithImageAtIndex:index duration:duration]) { - [self switchViewToMode:ViewModeEncoding]; + [self switchStateToHUD:self.encodingHUD]; } } -- (IBAction) toggleMoviePreviewPlayPause: (id) sender +- (void)updateProgress:(double)progress info:(NSString *)progressInfo { - // make sure a movie is even loaded up - if (self.movie) - { - if ([self.movie isPlaying]) // we are playing - { - [fMovieView pause:self.movie]; - [fPlayPauseButton setState: NSOnState]; - } - else // we are paused or stopped - { - [fMovieView play:self.movie]; - [fPlayPauseButton setState: NSOffState]; - } - } + self.encodingHUD.progress = progress; + self.encodingHUD.info = progressInfo; } -- (IBAction) moviePlaybackGoToBeginning: (id) sender +- (void)didCancelMovieCreation { - [fMovieView gotoBeginning:self.movie]; + [self switchStateToHUD:self.pictureHUD]; } -- (IBAction) moviePlaybackGoToEnd: (id) sender +- (void)showAlert:(NSURL *)fileURL; { - [fMovieView gotoEnd:self.movie]; + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"HandBrake can't open the preview.", nil) + defaultButton:NSLocalizedString(@"Open in external player", nil) + alternateButton:NSLocalizedString(@"Cancel", nil) + otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"HandBrake can't playback this combination of video/audio/container format. Do you want to open it in an external player?", nil)]; + + [alert beginSheetModalForWindow:self.window modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:(void *)CFBridgingRetain(fileURL)]; } -- (void) startMovieTimer +- (void)alertDidEnd:(NSAlert *)alert + returnCode:(NSInteger)returnCode + contextInfo:(void *)contextInfo { - if (!self.movieTimer) + NSURL *fileURL = CFBridgingRelease(contextInfo); + if (returnCode == NSModalResponseOK) { - self.movieTimer = [NSTimer scheduledTimerWithTimeInterval:0.09 target:self - selector:@selector(movieTimerFired:) - userInfo:nil repeats:YES]; + [[NSWorkspace sharedWorkspace] openURL:fileURL]; } } -- (void) stopMovieTimer -{ - [self.movieTimer invalidate]; - self.movieTimer = nil; -} - -- (void) movieTimerFired: (NSTimer *)theTimer +- (void)setUpPlaybackOfURL:(NSURL *)fileURL; { - if (self.movie != nil) + if (self.player.isPlayable && self.currentHUD == self.encodingHUD) { - [self adjustPreviewScrubberForCurrentMovieTime]; - [fMovieInfoField setStringValue: [self.movie timecode]]; + [self switchStateToHUD:self.playerHUD]; + } + else + { + [self showAlert:fileURL]; + [self switchStateToHUD:self.pictureHUD]; } } -- (IBAction) showPicturesPreview: (id) sender +- (void)didCreateMovieAtURL:(NSURL *)fileURL { - [self switchViewToMode:ViewModePicturePreview]; -} + if (fileURL) + { + self.player = [[HBAVPlayer alloc] initWithURL:fileURL]; -#pragma mark - -#pragma mark Movie Playback Scrubber + if (self.player) + { + [self.player loadPlayableValueAsynchronouslyWithCompletionHandler:^{ -// Initialize the preview scrubber min/max to appropriate values for the current movie -- (void) initPreviewScrubberForMovie -{ - QTTime duration = [self.movie duration]; - CGFloat result = duration.timeValue / duration.timeScale; + dispatch_async(dispatch_get_main_queue(), ^{ + [self setUpPlaybackOfURL:fileURL]; + }); - [fMovieScrubberSlider setMinValue:0.0]; - [fMovieScrubberSlider setMaxValue: result]; - [fMovieScrubberSlider setDoubleValue: 0.0]; + }]; + } + else + { + [self showAlert:fileURL]; + [self switchStateToHUD:self.pictureHUD]; + } + } } -- (void) adjustPreviewScrubberForCurrentMovieTime -{ - QTTime time = [self.movie currentTime]; - - CGFloat result = (CGFloat)time.timeValue / (CGFloat)time.timeScale;; - [fMovieScrubberSlider setDoubleValue:result]; -} +#pragma mark - Player mode -- (IBAction) previewScrubberChanged: (id) sender +- (void)enterPlayerState { - [fMovieView pause:self.movie]; - [self.movie setCurrentTimeDouble:[fMovieScrubberSlider doubleValue]]; - [fMovieInfoField setStringValue: [self.movie timecode]]; -} + // Scale the layer to the picture player size + CALayer *playerLayer = self.player.layer; + playerLayer.frame = self.previewView.pictureFrame; -#pragma mark - -#pragma mark Movie Notifications + [self.window.contentView.layer insertSublayer:playerLayer atIndex:1]; -- (void) addMovieObservers -{ - // Notification for any time the movie rate changes - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(movieRateDidChange:) - name:@"QTMovieRateDidChangeNotification" - object:self.movie]; + self.playerHUD.player = self.player; } -- (void) removeMovieObservers +- (void)exitPlayerState { - // Notification for any time the movie rate changes - [[NSNotificationCenter defaultCenter] removeObserver:self - name:@"QTMovieRateDidChangeNotification" - object:self.movie]; + self.playerHUD.player = nil; + [self.player pause]; + [self.player.layer removeFromSuperlayer]; + self.player = nil; } -- (void) movieRateDidChange: (NSNotification *) notification +- (void)stopPlayer { - if (self.movie.isPlaying) - [fPlayPauseButton setState: NSOnState]; - else - [fPlayPauseButton setState: NSOffState]; + [self switchStateToHUD:self.pictureHUD]; } -#pragma mark - -#pragma mark Keyboard and mouse wheel control +#pragma mark - Scroll -/* fMovieView Keyboard controls */ -- (void) keyDown: (NSEvent *) event +- (void)keyDown:(NSEvent *)event { - unichar key = [[event charactersIgnoringModifiers] characterAtIndex:0]; - QTMovie *movie = self.movie; - - if (movie) - { - if (key == 32) - { - if ([movie isPlaying]) - [fMovieView pause:movie]; - else - [fMovieView play:movie]; - } - else if (key == 'k') - [fMovieView pause:movie]; - else if (key == 'l') - { - float rate = [movie rate]; - rate += 1.0f; - [fMovieView play:movie]; - [movie setRate:rate]; - } - else if (key == 'j') - { - float rate = [movie rate]; - rate -= 1.0f; - [fMovieView play:movie]; - [movie setRate:rate]; - } - else if ([event modifierFlags] & NSAlternateKeyMask && key == NSLeftArrowFunctionKey) - [fMovieView gotoBeginning:self]; - else if ([event modifierFlags] & NSAlternateKeyMask && key == NSRightArrowFunctionKey) - [fMovieView gotoEnd:self]; - else if (key == NSLeftArrowFunctionKey) - [fMovieView stepBackward:self]; - else if (key == NSRightArrowFunctionKey) - [fMovieView stepForward:self]; - else - [super keyDown:event]; - } - else if (self.currentViewMode != ViewModeEncoding) + if ([self.currentHUD HB_keyDown:event] == NO) { - if (key == NSLeftArrowFunctionKey) - { - [fPictureSlider setIntegerValue:self.pictureIndex > [fPictureSlider minValue] ? self.pictureIndex - 1 : self.pictureIndex]; - [self pictureSliderChanged:self]; - } - else if (key == NSRightArrowFunctionKey) - { - [fPictureSlider setIntegerValue:self.pictureIndex < [fPictureSlider maxValue] ? self.pictureIndex + 1 : self.pictureIndex]; - [self pictureSliderChanged:self]; - } - else - [super keyDown:event]; - } - else [super keyDown:event]; + } } -- (void)scrollWheel:(NSEvent *)theEvent +- (void)scrollWheel:(NSEvent *)event { - if (self.currentViewMode != ViewModeEncoding) + if ([self.currentHUD HB_scrollWheel:event] == NO) { - if (theEvent.deltaY < 0) - { - [fPictureSlider setIntegerValue:self.pictureIndex < [fPictureSlider maxValue] ? self.pictureIndex + 1 : self.pictureIndex]; - [self pictureSliderChanged:self]; - } - else if (theEvent.deltaY > 0) - { - [fPictureSlider setIntegerValue:self.pictureIndex > [fPictureSlider minValue] ? self.pictureIndex - 1 : self.pictureIndex]; - [self pictureSliderChanged:self]; - } + [super scrollWheel:event]; } } diff --git a/macosx/HBQTKitPlayer.h b/macosx/HBQTKitPlayer.h new file mode 100644 index 000000000..a9a9de54a --- /dev/null +++ b/macosx/HBQTKitPlayer.h @@ -0,0 +1,28 @@ +/* HBQTKitPlayer.h $ + + 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 <Foundation/Foundation.h> +#import <QuartzCore/QuartzCore.h> + +#import "HBPlayer.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface HBQTKitPlayer : NSObject <HBPlayer> + +@property (nonatomic, readonly) CALayer *layer; + +@property (nonatomic, readonly) NSTimeInterval duration; +@property (nonatomic) NSTimeInterval currentTime; + +@property (nonatomic) float rate; +@property (nonatomic) float volume; + +@property (nonatomic, readonly, getter=isPlayable) BOOL playable; + +@end + +NS_ASSUME_NONNULL_END 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 diff --git a/macosx/HandBrake.xcodeproj/project.pbxproj b/macosx/HandBrake.xcodeproj/project.pbxproj index e83fe377d..1deb670c4 100644 --- a/macosx/HandBrake.xcodeproj/project.pbxproj +++ b/macosx/HandBrake.xcodeproj/project.pbxproj @@ -63,13 +63,13 @@ 27D6C77314B102DA00B785E4 /* libxml2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C74014B102DA00B785E4 /* libxml2.a */; }; 3490BCB41614CF8D002A5AD7 /* HandBrake.icns in Resources */ = {isa = PBXBuildFile; fileRef = 3490BCB31614CF8D002A5AD7 /* HandBrake.icns */; }; 6F0D69A91AD0683100A39DCA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 273F204014ADBC210021BE6D /* Foundation.framework */; }; + A903C5601CCE78060026B0ED /* NSWindow+HBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A903C55F1CCE78060026B0ED /* NSWindow+HBAdditions.m */; }; A91119A21C7DD58B001C463C /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 273F203B14ADBC210021BE6D /* Cocoa.framework */; }; A91119A31C7DD591001C463C /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 273F202214ADB8650021BE6D /* IOKit.framework */; }; A91119A41C7DD614001C463C /* HBSubtitlesDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = A9F4728B1976BAA70009EC65 /* HBSubtitlesDefaults.h */; settings = {ATTRIBUTES = (Public, ); }; }; A91119A51C7DD644001C463C /* HBDistributedArray.h in Headers */ = {isa = PBXBuildFile; fileRef = A9E66D6E1A67A2A8007B641D /* HBDistributedArray.h */; settings = {ATTRIBUTES = (Public, ); }; }; A91119A61C7DD64A001C463C /* HBDistributedArray.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E66D6F1A67A2A8007B641D /* HBDistributedArray.m */; }; A914BCB31BC441C700157917 /* HBPreviewView.m in Sources */ = {isa = PBXBuildFile; fileRef = A914BCB21BC441C700157917 /* HBPreviewView.m */; }; - A914BCB61BC441D100157917 /* QTKit+HBQTMovieExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = A914BCB51BC441D100157917 /* QTKit+HBQTMovieExtensions.m */; }; A916180E1C845161000556C6 /* NSDictionary+HBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A93B0DF71C804CF50051A3FA /* NSDictionary+HBAdditions.m */; }; A916C9921C84498F00C7B560 /* DockTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 46AB433415F98A2B009C0961 /* DockTextField.m */; }; A916C9931C8449A100C7B560 /* HBAddPresetController.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E2FD251A21BC4A000E8D3F /* HBAddPresetController.m */; }; @@ -170,12 +170,21 @@ A92268781A6E555500A8D5C5 /* HBAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A92268771A6E555500A8D5C5 /* HBAppDelegate.m */; }; A922687B1A6E569B00A8D5C5 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = A92268791A6E569B00A8D5C5 /* MainWindow.xib */; }; A932E26C1988334B0047D13E /* AudioDefaults.xib in Resources */ = {isa = PBXBuildFile; fileRef = A932E26A1988334B0047D13E /* AudioDefaults.xib */; }; + A9350F501CCA7F490089F970 /* HBQTKitPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = A9350F4E1CCA7C3B0089F970 /* HBQTKitPlayer.m */; }; A937EECB1C6C7C0300EEAE6D /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = A937EECA1C6C7C0300EEAE6D /* dsa_pub.pem */; }; A93E0ED31972957000FD67FB /* HBVideoController.m in Sources */ = {isa = PBXBuildFile; fileRef = A93E0ED11972957000FD67FB /* HBVideoController.m */; }; A93E0ED71972958C00FD67FB /* Video.xib in Resources */ = {isa = PBXBuildFile; fileRef = A93E0ED51972958C00FD67FB /* Video.xib */; }; A94A98F51C858EFB004BA9BA /* HBDictTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A94A98F41C858EFB004BA9BA /* HBDictTests.m */; }; A95121E61B5F7BE700FD773D /* NSArray+HBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A95121E51B5F7BE700FD773D /* NSArray+HBAdditions.m */; }; A955128B1A320B02001BFC6F /* libjansson.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A95512881A320A12001BFC6F /* libjansson.a */; }; + A95BC1E71CD2548A008D6A33 /* volHighTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A95BC1E51CD2548A008D6A33 /* volHighTemplate.pdf */; }; + A95BC1E81CD2548A008D6A33 /* volLowTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A95BC1E61CD2548A008D6A33 /* volLowTemplate.pdf */; }; + A96664B01CCE45BF00DA4A57 /* HBPlayerHUDController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96664AE1CCE45BF00DA4A57 /* HBPlayerHUDController.m */; }; + A96664B11CCE45BF00DA4A57 /* HBPlayerHUDController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A96664AF1CCE45BF00DA4A57 /* HBPlayerHUDController.xib */; }; + A96664B51CCE48F700DA4A57 /* HBPictureHUDController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96664B31CCE48F700DA4A57 /* HBPictureHUDController.m */; }; + A96664B61CCE48F700DA4A57 /* HBPictureHUDController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A96664B41CCE48F700DA4A57 /* HBPictureHUDController.xib */; }; + A96664BA1CCE493D00DA4A57 /* HBEncodingProgressHUDController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96664B81CCE493D00DA4A57 /* HBEncodingProgressHUDController.m */; }; + A96664BB1CCE493D00DA4A57 /* HBEncodingProgressHUDController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A96664B91CCE493D00DA4A57 /* HBEncodingProgressHUDController.xib */; }; A9706CB41AC1436F00BAEAA8 /* HBApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = A9706CB31AC1436F00BAEAA8 /* HBApplication.m */; }; A9706CB71AC1437800BAEAA8 /* HBExceptionAlertController.m in Sources */ = {isa = PBXBuildFile; fileRef = A9706CB61AC1437800BAEAA8 /* HBExceptionAlertController.m */; }; A9706CBA1AC1452800BAEAA8 /* ExceptionAlert.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9706CB81AC1452800BAEAA8 /* ExceptionAlert.xib */; }; @@ -184,6 +193,7 @@ A9736F171C7DA5FE008F1D18 /* HandBrakeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9736F021C7DA5FE008F1D18 /* HandBrakeKit.framework */; }; A9736F181C7DA5FE008F1D18 /* HandBrakeKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = A9736F021C7DA5FE008F1D18 /* HandBrakeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A9736F1F1C7DA667008F1D18 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 273F204014ADBC210021BE6D /* Foundation.framework */; }; + A98036CD1CCA91DD007661AA /* HBAVPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = A98036CC1CCA91DD007661AA /* HBAVPlayer.m */; }; A98B8E241C7DD2A200B810C9 /* HBPresetCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = A997D8EB1A4ABB0900E19B6F /* HBPresetCoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; A98C29C41977B10600AF5DED /* HBLanguagesSelection.m in Sources */ = {isa = PBXBuildFile; fileRef = A98C29C31977B10600AF5DED /* HBLanguagesSelection.m */; }; A98F38071C7DCA7E00E469C8 /* HBStateFormatter+Private.m in Sources */ = {isa = PBXBuildFile; fileRef = A98F38051C7DCA7E00E469C8 /* HBStateFormatter+Private.m */; }; @@ -191,6 +201,7 @@ A9935213196F38A70069C6B7 /* ChaptersTitles.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9935211196F38A70069C6B7 /* ChaptersTitles.xib */; }; A99F40CF1B624E7E00750170 /* HBPictureViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A99F40CD1B624E7E00750170 /* HBPictureViewController.m */; }; A99F40D31B624EA500750170 /* HBPictureViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A99F40D11B624EA500750170 /* HBPictureViewController.xib */; }; + A9A0CBE81CCEA3670045B3DF /* HBPlayerTrack.m in Sources */ = {isa = PBXBuildFile; fileRef = A9A0CBE61CCEA1D10045B3DF /* HBPlayerTrack.m */; }; A9BB0F2719A0ECE40079F1C1 /* HBHUDButtonCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A9BB0F2619A0ECE40079F1C1 /* HBHUDButtonCell.m */; }; A9BC24C91A69293E007DC41A /* HBAttributedStringAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A9BC24C81A69293E007DC41A /* HBAttributedStringAdditions.m */; }; A9C0DB85197E7B0000DF55B3 /* SubtitlesDefaults.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9C0DB83197E7B0000DF55B3 /* SubtitlesDefaults.xib */; }; @@ -207,10 +218,10 @@ A9DF49291C884C4E008AC14A /* HBMockTitle.m in Sources */ = {isa = PBXBuildFile; fileRef = A9DF49251C884C4E008AC14A /* HBMockTitle.m */; }; A9DF492A1C884C4E008AC14A /* HBPresetsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A9DF49261C884C4E008AC14A /* HBPresetsTests.m */; }; A9E1467B16BC2ABD00C307BC /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9E1467A16BC2ABD00C307BC /* QuartzCore.framework */; }; - A9E1468016BC2AD800C307BC /* next-p.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A9E1467C16BC2AD800C307BC /* next-p.pdf */; }; - A9E1468116BC2AD800C307BC /* pause-p.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A9E1467D16BC2AD800C307BC /* pause-p.pdf */; }; - A9E1468216BC2AD800C307BC /* play-p.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A9E1467E16BC2AD800C307BC /* play-p.pdf */; }; - A9E1468316BC2AD800C307BC /* prev-p.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A9E1467F16BC2AD800C307BC /* prev-p.pdf */; }; + A9E1468016BC2AD800C307BC /* NextTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A9E1467C16BC2AD800C307BC /* NextTemplate.pdf */; }; + A9E1468116BC2AD800C307BC /* PauseTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A9E1467D16BC2AD800C307BC /* PauseTemplate.pdf */; }; + A9E1468216BC2AD800C307BC /* PlayTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A9E1467E16BC2AD800C307BC /* PlayTemplate.pdf */; }; + A9E1468316BC2AD800C307BC /* PrevTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A9E1467F16BC2AD800C307BC /* PrevTemplate.pdf */; }; A9E165521C523016003EF30E /* libavfilter.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A9E165511C523016003EF30E /* libavfilter.a */; }; A9E2FD2B1A21BC6F000E8D3F /* AddPreset.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9E2FD291A21BC6F000E8D3F /* AddPreset.xib */; }; A9F2EB6F196F12C800066546 /* Audio.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9F2EB6D196F12C800066546 /* Audio.xib */; }; @@ -371,14 +382,14 @@ 3490BCB31614CF8D002A5AD7 /* HandBrake.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = HandBrake.icns; sourceTree = "<group>"; }; 46AB433315F98A2B009C0961 /* DockTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DockTextField.h; sourceTree = "<group>"; }; 46AB433415F98A2B009C0961 /* DockTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DockTextField.m; sourceTree = "<group>"; }; + A903C55E1CCE78060026B0ED /* NSWindow+HBAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSWindow+HBAdditions.h"; sourceTree = "<group>"; }; + A903C55F1CCE78060026B0ED /* NSWindow+HBAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSWindow+HBAdditions.m"; sourceTree = "<group>"; }; A90A0CAD1988D57200DA65CE /* HBAudioTrackPreset.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBAudioTrackPreset.h; sourceTree = "<group>"; }; A90A0CAE1988D57200DA65CE /* HBAudioTrackPreset.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBAudioTrackPreset.m; sourceTree = "<group>"; }; A91017B21A64440A00039BFB /* HBSubtitles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBSubtitles.h; sourceTree = "<group>"; }; A91017B31A64440A00039BFB /* HBSubtitles.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBSubtitles.m; sourceTree = "<group>"; }; A914BCB11BC441C700157917 /* HBPreviewView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBPreviewView.h; sourceTree = "<group>"; }; A914BCB21BC441C700157917 /* HBPreviewView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBPreviewView.m; sourceTree = "<group>"; }; - A914BCB41BC441D100157917 /* QTKit+HBQTMovieExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "QTKit+HBQTMovieExtensions.h"; sourceTree = "<group>"; }; - A914BCB51BC441D100157917 /* QTKit+HBQTMovieExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "QTKit+HBQTMovieExtensions.m"; sourceTree = "<group>"; }; A9160A331AE7A165009A7818 /* HBCodingUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBCodingUtilities.h; sourceTree = "<group>"; }; A9160A341AE7A165009A7818 /* HBCodingUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBCodingUtilities.m; sourceTree = "<group>"; }; A91726E5197291BC00D1AFEF /* HBChapterTitlesController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBChapterTitlesController.h; sourceTree = "<group>"; }; @@ -403,6 +414,8 @@ A932E26E198833920047D13E /* HBAudioDefaultsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBAudioDefaultsController.m; sourceTree = "<group>"; }; A932E271198834130047D13E /* HBAudioDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBAudioDefaults.h; sourceTree = "<group>"; }; A932E272198834130047D13E /* HBAudioDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBAudioDefaults.m; sourceTree = "<group>"; }; + A9350F4D1CCA7C3B0089F970 /* HBQTKitPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBQTKitPlayer.h; sourceTree = "<group>"; }; + A9350F4E1CCA7C3B0089F970 /* HBQTKitPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBQTKitPlayer.m; sourceTree = "<group>"; }; A937EECA1C6C7C0300EEAE6D /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dsa_pub.pem; sourceTree = "<group>"; }; A93B0DF61C804CF50051A3FA /* NSDictionary+HBAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+HBAdditions.h"; sourceTree = "<group>"; }; A93B0DF71C804CF50051A3FA /* NSDictionary+HBAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+HBAdditions.m"; sourceTree = "<group>"; }; @@ -411,6 +424,7 @@ A93E0ED61972958C00FD67FB /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Video.xib; sourceTree = "<group>"; }; A93FD4731A62ABE800A6AC43 /* HBAudio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBAudio.h; sourceTree = "<group>"; }; A93FD4741A62ABE800A6AC43 /* HBAudio.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBAudio.m; sourceTree = "<group>"; }; + A941ACB91CD75B4E0029D06A /* HBHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBHUD.h; sourceTree = "<group>"; }; A94A98F41C858EFB004BA9BA /* HBDictTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBDictTests.m; sourceTree = "<group>"; }; A95121E41B5F7BE700FD773D /* NSArray+HBAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+HBAdditions.h"; sourceTree = "<group>"; }; A95121E51B5F7BE700FD773D /* NSArray+HBAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+HBAdditions.m"; sourceTree = "<group>"; }; @@ -427,6 +441,17 @@ A95512881A320A12001BFC6F /* libjansson.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libjansson.a; path = external/contrib/lib/libjansson.a; sourceTree = BUILT_PRODUCTS_DIR; }; A9597A281A49749D00007771 /* HBRange+UIAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "HBRange+UIAdditions.h"; sourceTree = "<group>"; }; A9597A291A49749D00007771 /* HBRange+UIAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "HBRange+UIAdditions.m"; sourceTree = "<group>"; }; + A95BC1E51CD2548A008D6A33 /* volHighTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = volHighTemplate.pdf; sourceTree = "<group>"; }; + A95BC1E61CD2548A008D6A33 /* volLowTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = volLowTemplate.pdf; sourceTree = "<group>"; }; + A96664AD1CCE45BF00DA4A57 /* HBPlayerHUDController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBPlayerHUDController.h; sourceTree = "<group>"; }; + A96664AE1CCE45BF00DA4A57 /* HBPlayerHUDController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBPlayerHUDController.m; sourceTree = "<group>"; }; + A96664AF1CCE45BF00DA4A57 /* HBPlayerHUDController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HBPlayerHUDController.xib; sourceTree = "<group>"; }; + A96664B21CCE48F700DA4A57 /* HBPictureHUDController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBPictureHUDController.h; sourceTree = "<group>"; }; + A96664B31CCE48F700DA4A57 /* HBPictureHUDController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBPictureHUDController.m; sourceTree = "<group>"; }; + A96664B41CCE48F700DA4A57 /* HBPictureHUDController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HBPictureHUDController.xib; sourceTree = "<group>"; }; + A96664B71CCE493D00DA4A57 /* HBEncodingProgressHUDController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBEncodingProgressHUDController.h; sourceTree = "<group>"; }; + A96664B81CCE493D00DA4A57 /* HBEncodingProgressHUDController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBEncodingProgressHUDController.m; sourceTree = "<group>"; }; + A96664B91CCE493D00DA4A57 /* HBEncodingProgressHUDController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HBEncodingProgressHUDController.xib; sourceTree = "<group>"; }; A96CD1741BCC5F9100F372F1 /* HBMutablePreset.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBMutablePreset.h; sourceTree = "<group>"; }; A96CD1751BCC5F9100F372F1 /* HBMutablePreset.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBMutablePreset.m; sourceTree = "<group>"; }; A9706CB21AC1436F00BAEAA8 /* HBApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBApplication.h; sourceTree = "<group>"; }; @@ -443,6 +468,8 @@ A9736F141C7DA5FE008F1D18 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; A975C08C1AE8C5270061870D /* HBStateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBStateFormatter.h; sourceTree = "<group>"; }; A975C08D1AE8C5270061870D /* HBStateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBStateFormatter.m; sourceTree = "<group>"; }; + A98036CB1CCA91DD007661AA /* HBAVPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBAVPlayer.h; sourceTree = "<group>"; }; + A98036CC1CCA91DD007661AA /* HBAVPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBAVPlayer.m; sourceTree = "<group>"; }; A988AF9B1BC7C35F00932543 /* HBChapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBChapter.h; sourceTree = "<group>"; }; A988AF9C1BC7C35F00932543 /* HBChapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBChapter.m; sourceTree = "<group>"; }; A98C29C21977B10600AF5DED /* HBLanguagesSelection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBLanguagesSelection.h; sourceTree = "<group>"; }; @@ -460,6 +487,8 @@ A99F40CC1B624E7E00750170 /* HBPictureViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBPictureViewController.h; sourceTree = "<group>"; }; A99F40CD1B624E7E00750170 /* HBPictureViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBPictureViewController.m; sourceTree = "<group>"; }; A99F40D21B624EA500750170 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = HBPictureViewController.xib; sourceTree = "<group>"; }; + A9A0CBE51CCEA1D10045B3DF /* HBPlayerTrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBPlayerTrack.h; sourceTree = "<group>"; }; + A9A0CBE61CCEA1D10045B3DF /* HBPlayerTrack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBPlayerTrack.m; sourceTree = "<group>"; }; A9AA44781970664A00D7DEFC /* HBUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HBUtilities.h; path = ../HBUtilities.h; sourceTree = "<group>"; }; A9AA44791970664A00D7DEFC /* HBUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HBUtilities.m; path = ../HBUtilities.m; sourceTree = "<group>"; }; A9AA447B1970724D00D7DEFC /* HBAdvancedController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBAdvancedController.h; sourceTree = "<group>"; }; @@ -476,6 +505,7 @@ A9C1839C1A716BCC00C897C2 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = HBTitleSelection.xib; sourceTree = "<group>"; }; A9C9F88719A733FE00DC8923 /* HBHUDView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBHUDView.h; sourceTree = "<group>"; }; A9C9F88819A733FE00DC8923 /* HBHUDView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBHUDView.m; sourceTree = "<group>"; }; + A9CAC26E1CCB6B0F00A39E72 /* HBPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBPlayer.h; sourceTree = "<group>"; }; A9CF25F01990D62C0023F727 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Presets.xib; sourceTree = "<group>"; }; A9CF25F21990D64E0023F727 /* HBPreset.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBPreset.h; sourceTree = "<group>"; }; A9CF25F31990D64E0023F727 /* HBPreset.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBPreset.m; sourceTree = "<group>"; }; @@ -505,10 +535,10 @@ A9DF49251C884C4E008AC14A /* HBMockTitle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBMockTitle.m; sourceTree = "<group>"; }; A9DF49261C884C4E008AC14A /* HBPresetsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBPresetsTests.m; sourceTree = "<group>"; }; A9E1467A16BC2ABD00C307BC /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = /System/Library/Frameworks/QuartzCore.framework; sourceTree = "<absolute>"; }; - A9E1467C16BC2AD800C307BC /* next-p.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "next-p.pdf"; sourceTree = "<group>"; }; - A9E1467D16BC2AD800C307BC /* pause-p.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "pause-p.pdf"; sourceTree = "<group>"; }; - A9E1467E16BC2AD800C307BC /* play-p.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "play-p.pdf"; sourceTree = "<group>"; }; - A9E1467F16BC2AD800C307BC /* prev-p.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "prev-p.pdf"; sourceTree = "<group>"; }; + A9E1467C16BC2AD800C307BC /* NextTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = NextTemplate.pdf; sourceTree = "<group>"; }; + A9E1467D16BC2AD800C307BC /* PauseTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = PauseTemplate.pdf; sourceTree = "<group>"; }; + A9E1467E16BC2AD800C307BC /* PlayTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = PlayTemplate.pdf; sourceTree = "<group>"; }; + A9E1467F16BC2AD800C307BC /* PrevTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = PrevTemplate.pdf; sourceTree = "<group>"; }; A9E165511C523016003EF30E /* libavfilter.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavfilter.a; path = external/contrib/lib/libavfilter.a; sourceTree = BUILT_PRODUCTS_DIR; }; A9E2FD241A21BC4A000E8D3F /* HBAddPresetController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBAddPresetController.h; sourceTree = "<group>"; }; A9E2FD251A21BC4A000E8D3F /* HBAddPresetController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBAddPresetController.m; sourceTree = "<group>"; }; @@ -775,10 +805,12 @@ 273F212014ADCBF70021BE6D /* pdf icons */ = { isa = PBXGroup; children = ( - A9E1467C16BC2AD800C307BC /* next-p.pdf */, - A9E1467D16BC2AD800C307BC /* pause-p.pdf */, - A9E1467E16BC2AD800C307BC /* play-p.pdf */, - A9E1467F16BC2AD800C307BC /* prev-p.pdf */, + A9E1467C16BC2AD800C307BC /* NextTemplate.pdf */, + A9E1467D16BC2AD800C307BC /* PauseTemplate.pdf */, + A9E1467E16BC2AD800C307BC /* PlayTemplate.pdf */, + A9E1467F16BC2AD800C307BC /* PrevTemplate.pdf */, + A95BC1E51CD2548A008D6A33 /* volHighTemplate.pdf */, + A95BC1E61CD2548A008D6A33 /* volLowTemplate.pdf */, ); name = "pdf icons"; path = icons; @@ -873,10 +905,8 @@ A901C2411BC7CFFD00D77735 /* Preview */ = { isa = PBXGroup; children = ( - A9BB0F2519A0ECE40079F1C1 /* HBHUDButtonCell.h */, - A9BB0F2619A0ECE40079F1C1 /* HBHUDButtonCell.m */, - A9C9F88719A733FE00DC8923 /* HBHUDView.h */, - A9C9F88819A733FE00DC8923 /* HBHUDView.m */, + A903C55E1CCE78060026B0ED /* NSWindow+HBAdditions.h */, + A903C55F1CCE78060026B0ED /* NSWindow+HBAdditions.m */, A914BCB11BC441C700157917 /* HBPreviewView.h */, A914BCB21BC441C700157917 /* HBPreviewView.m */, 273F20A914ADBE670021BE6D /* HBPictureController.h */, @@ -885,6 +915,8 @@ 273F20A414ADBE670021BE6D /* HBPreviewController.m */, A9AA447D1970729300D7DEFC /* HBPreviewGenerator.h */, A9D1E41618262364002F6424 /* HBPreviewGenerator.m */, + A93E6F2E1CD774960096C0DE /* Player */, + A9CAC26D1CCB6AE500A39E72 /* HUD */, ); name = Preview; sourceTree = "<group>"; @@ -916,8 +948,6 @@ A9706CB61AC1437800BAEAA8 /* HBExceptionAlertController.m */, A95121E41B5F7BE700FD773D /* NSArray+HBAdditions.h */, A95121E51B5F7BE700FD773D /* NSArray+HBAdditions.m */, - A914BCB41BC441D100157917 /* QTKit+HBQTMovieExtensions.h */, - A914BCB51BC441D100157917 /* QTKit+HBQTMovieExtensions.m */, 273F20BD14ADC09F0021BE6D /* main.mm */, ); name = Others; @@ -945,6 +975,20 @@ name = "Audio Defaults"; sourceTree = "<group>"; }; + A93E6F2E1CD774960096C0DE /* Player */ = { + isa = PBXGroup; + children = ( + A9CAC26E1CCB6B0F00A39E72 /* HBPlayer.h */, + A9A0CBE51CCEA1D10045B3DF /* HBPlayerTrack.h */, + A9A0CBE61CCEA1D10045B3DF /* HBPlayerTrack.m */, + A9350F4D1CCA7C3B0089F970 /* HBQTKitPlayer.h */, + A9350F4E1CCA7C3B0089F970 /* HBQTKitPlayer.m */, + A98036CB1CCA91DD007661AA /* HBAVPlayer.h */, + A98036CC1CCA91DD007661AA /* HBAVPlayer.m */, + ); + name = Player; + sourceTree = "<group>"; + }; A952392E199A647F00588AEF /* Presets */ = { isa = PBXGroup; children = ( @@ -1130,6 +1174,27 @@ name = "UI Views"; sourceTree = "<group>"; }; + A9CAC26D1CCB6AE500A39E72 /* HUD */ = { + isa = PBXGroup; + children = ( + A941ACB91CD75B4E0029D06A /* HBHUD.h */, + A9BB0F2519A0ECE40079F1C1 /* HBHUDButtonCell.h */, + A9BB0F2619A0ECE40079F1C1 /* HBHUDButtonCell.m */, + A9C9F88719A733FE00DC8923 /* HBHUDView.h */, + A9C9F88819A733FE00DC8923 /* HBHUDView.m */, + A96664B21CCE48F700DA4A57 /* HBPictureHUDController.h */, + A96664B31CCE48F700DA4A57 /* HBPictureHUDController.m */, + A96664B41CCE48F700DA4A57 /* HBPictureHUDController.xib */, + A96664B71CCE493D00DA4A57 /* HBEncodingProgressHUDController.h */, + A96664B81CCE493D00DA4A57 /* HBEncodingProgressHUDController.m */, + A96664B91CCE493D00DA4A57 /* HBEncodingProgressHUDController.xib */, + A96664AD1CCE45BF00DA4A57 /* HBPlayerHUDController.h */, + A96664AE1CCE45BF00DA4A57 /* HBPlayerHUDController.m */, + A96664AF1CCE45BF00DA4A57 /* HBPlayerHUDController.xib */, + ); + name = HUD; + sourceTree = "<group>"; + }; A9F472851976B7AA0009EC65 /* Subtitles Defaults */ = { isa = PBXGroup; children = ( @@ -1319,6 +1384,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A96664B61CCE48F700DA4A57 /* HBPictureHUDController.xib in Resources */, A9F2EB6F196F12C800066546 /* Audio.xib in Resources */, A9CF25F11990D62C0023F727 /* Presets.xib in Resources */, A9C1839D1A716BCC00C897C2 /* HBTitleSelection.xib in Resources */, @@ -1332,14 +1398,18 @@ D86C9DD51C6D372500F06F1B /* Assets.xcassets in Resources */, 273F218F14ADDDA10021BE6D /* PictureSettings.xib in Resources */, 273F219014ADDDA10021BE6D /* Preferences.xib in Resources */, + A95BC1E71CD2548A008D6A33 /* volHighTemplate.pdf in Resources */, A922687B1A6E569B00A8D5C5 /* MainWindow.xib in Resources */, + A96664B11CCE45BF00DA4A57 /* HBPlayerHUDController.xib in Resources */, 273F219114ADDDA10021BE6D /* Queue.xib in Resources */, 3490BCB41614CF8D002A5AD7 /* HandBrake.icns in Resources */, - A9E1468016BC2AD800C307BC /* next-p.pdf in Resources */, - A9E1468116BC2AD800C307BC /* pause-p.pdf in Resources */, - A9E1468216BC2AD800C307BC /* play-p.pdf in Resources */, - A9E1468316BC2AD800C307BC /* prev-p.pdf in Resources */, + A9E1468016BC2AD800C307BC /* NextTemplate.pdf in Resources */, + A9E1468116BC2AD800C307BC /* PauseTemplate.pdf in Resources */, + A9E1468216BC2AD800C307BC /* PlayTemplate.pdf in Resources */, + A96664BB1CCE493D00DA4A57 /* HBEncodingProgressHUDController.xib in Resources */, + A9E1468316BC2AD800C307BC /* PrevTemplate.pdf in Resources */, A937EECB1C6C7C0300EEAE6D /* dsa_pub.pem in Resources */, + A95BC1E81CD2548A008D6A33 /* volLowTemplate.pdf in Resources */, A9C0DB85197E7B0000DF55B3 /* SubtitlesDefaults.xib in Resources */, A9DC6C56196F0517002AE6B4 /* Subtitles.xib in Resources */, A99F40D31B624EA500750170 /* HBPictureViewController.xib in Resources */, @@ -1382,8 +1452,11 @@ files = ( A916C99B1C844A0800C7B560 /* HBQueueOutlineView.m in Sources */, A916C99A1C8449FB00C7B560 /* HBHUDView.m in Sources */, + A9350F501CCA7F490089F970 /* HBQTKitPlayer.m in Sources */, A916C9991C8449E200C7B560 /* main.mm in Sources */, A916C9981C8449DB00C7B560 /* HBTitleSelectionController.m in Sources */, + A903C5601CCE78060026B0ED /* NSWindow+HBAdditions.m in Sources */, + A9A0CBE81CCEA3670045B3DF /* HBPlayerTrack.m in Sources */, A916C9971C8449CA00C7B560 /* HBAudioDefaultsController.m in Sources */, A916C9961C8449BE00C7B560 /* HBJobOutputFileWriter.m in Sources */, A916C9951C8449B000C7B560 /* HBChapterTitlesController.m in Sources */, @@ -1393,6 +1466,7 @@ 273F20B514ADBE670021BE6D /* HBPreferencesController.m in Sources */, 273F20AC14ADBE670021BE6D /* HBController.m in Sources */, A93E0ED31972957000FD67FB /* HBVideoController.m in Sources */, + A96664B01CCE45BF00DA4A57 /* HBPlayerHUDController.m in Sources */, A99F40CF1B624E7E00750170 /* HBPictureViewController.m in Sources */, 273F20AF14ADBE670021BE6D /* HBAudioController.m in Sources */, A9DC6C52196F04F6002AE6B4 /* HBSubtitlesController.m in Sources */, @@ -1401,11 +1475,12 @@ A9906B2C1A710920001D82D5 /* HBQueueController.m in Sources */, A9F7102619A475EC00F61301 /* HBDockTile.m in Sources */, A98C29C41977B10600AF5DED /* HBLanguagesSelection.m in Sources */, + A96664BA1CCE493D00DA4A57 /* HBEncodingProgressHUDController.m in Sources */, A9BB0F2719A0ECE40079F1C1 /* HBHUDButtonCell.m in Sources */, A9706CB71AC1437800BAEAA8 /* HBExceptionAlertController.m in Sources */, A92268781A6E555500A8D5C5 /* HBAppDelegate.m in Sources */, + A98036CD1CCA91DD007661AA /* HBAVPlayer.m in Sources */, A9BC24C91A69293E007DC41A /* HBAttributedStringAdditions.m in Sources */, - A914BCB61BC441D100157917 /* QTKit+HBQTMovieExtensions.m in Sources */, 273F20B214ADBE670021BE6D /* HBImageAndTextCell.m in Sources */, 273F20B314ADBE670021BE6D /* HBOutputPanelController.m in Sources */, 273F20B414ADBE670021BE6D /* HBOutputRedirect.m in Sources */, @@ -1413,6 +1488,7 @@ A9D0FA7A1C1C36820003F2A9 /* HBTabView.m in Sources */, A91AFD0C1A948827009BECED /* HBOutputFileWriter.m in Sources */, A95121E61B5F7BE700FD773D /* NSArray+HBAdditions.m in Sources */, + A96664B51CCE48F700DA4A57 /* HBPictureHUDController.m in Sources */, A914BCB31BC441C700157917 /* HBPreviewView.m in Sources */, 273F20B714ADBE670021BE6D /* HBPreviewController.m in Sources */, A9D1E41718262364002F6424 /* HBPreviewGenerator.m in Sources */, diff --git a/macosx/NSWindow+HBAdditions.h b/macosx/NSWindow+HBAdditions.h new file mode 100644 index 000000000..e8ceb3c04 --- /dev/null +++ b/macosx/NSWindow+HBAdditions.h @@ -0,0 +1,25 @@ +// +// NSWindow+HBAdditions.h +// HandBrake +// +// Created by Damiano Galassi on 25/04/16. +// +// + +#import <Cocoa/Cocoa.h> + +@interface NSWindow (HBAdditions) + +/** + * Resizes the entire window to accomodate a view of a particular size. + */ +- (void)HB_resizeToBestSizeForViewSize:(NSSize)viewSize center:(NSPoint)center animate:(BOOL)performAnimation; + + +/** + * Calculates and returns the center point of the window + */ +- (NSPoint)HB_centerPoint; + + +@end diff --git a/macosx/NSWindow+HBAdditions.m b/macosx/NSWindow+HBAdditions.m new file mode 100644 index 000000000..2951d7a1f --- /dev/null +++ b/macosx/NSWindow+HBAdditions.m @@ -0,0 +1,79 @@ +// +// NSWindow+HBAdditions.m +// HandBrake +// +// Created by Damiano Galassi on 25/04/16. +// +// + +#import "NSWindow+HBAdditions.h" + +@implementation NSWindow (HBAdditions) + +- (void)HB_resizeToBestSizeForViewSize:(NSSize)viewSize center:(NSPoint)center animate:(BOOL)animateFlag +{ + NSSize currentSize = self.contentView.frame.size; + NSRect frame = self.frame; + + // Calculate border around content region of the frame + int borderX = (int)(frame.size.width - currentSize.width); + int borderY = (int)(frame.size.height - currentSize.height); + + // Make sure the frame is smaller than the screen + NSSize maxSize = self.screen.visibleFrame.size; + + // if we are not Scale To Screen, put an 10% of visible screen on the window + maxSize.width = maxSize.width * 0.90; + maxSize.height = maxSize.height * 0.90; + + // Set the new frame size + // Add the border to the new frame size so that the content region + // of the frame is large enough to accomodate the preview image + frame.size.width = viewSize.width + borderX; + frame.size.height = viewSize.height + borderY; + + // compare frame to max size of screen + if (frame.size.width > maxSize.width) + { + frame.size.width = maxSize.width; + } + if (frame.size.height > maxSize.height) + { + frame.size.height = maxSize.height; + } + + // Since upon launch we can open up the preview window if it was open + // the last time we quit (and at the size it was) we want to make + // sure that upon resize we do not have the window off the screen + // So check the origin against the screen origin and adjust if + // necessary. + NSSize screenSize = self.screen.visibleFrame.size; + NSPoint screenOrigin = self.screen.visibleFrame.origin; + + frame.origin.x = center.x - floor(frame.size.width / 2); + frame.origin.y = center.y - floor(frame.size.height / 2); + + // our origin is off the screen to the left + if (frame.origin.x < screenOrigin.x) + { + // so shift our origin to the right + frame.origin.x = screenOrigin.x; + } + else if ((frame.origin.x + frame.size.width) > (screenOrigin.x + screenSize.width)) + { + // the right side of the preview is off the screen, so shift to the left + frame.origin.x = (screenOrigin.x + screenSize.width) - frame.size.width; + } + + [self setFrame:frame display:YES animate:animateFlag]; +} + +- (NSPoint)HB_centerPoint +{ + NSPoint center = NSMakePoint(floor(self.frame.origin.x + self.frame.size.width / 2), + floor(self.frame.origin.y + self.frame.size.height / 2)); + return center; +} + + +@end diff --git a/macosx/QTKit+HBQTMovieExtensions.h b/macosx/QTKit+HBQTMovieExtensions.h deleted file mode 100644 index dadea3d1d..000000000 --- a/macosx/QTKit+HBQTMovieExtensions.h +++ /dev/null @@ -1,22 +0,0 @@ -/* QTKit+HBQTMovieExtensions.h - - 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 <Cocoa/Cocoa.h> -#import <QTKit/QTKit.h> - -@interface QTMovieView (HBQTMovieViewExtensions) - -- (void)mouseMoved:(NSEvent *)theEvent; - -@end - -@interface QTMovie (HBQTMovieExtensions) - -- (BOOL)isPlaying; -- (NSString *)timecode; -- (void)setCurrentTimeDouble:(double)value; - -@end diff --git a/macosx/QTKit+HBQTMovieExtensions.m b/macosx/QTKit+HBQTMovieExtensions.m deleted file mode 100644 index 903659ec9..000000000 --- a/macosx/QTKit+HBQTMovieExtensions.m +++ /dev/null @@ -1,49 +0,0 @@ -/* QTKit+HBQTMovieExtensions.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 "QTKit+HBQTMovieExtensions.h" - -@implementation QTMovieView (HBQTMovieViewExtensions) - -- (void)mouseMoved:(NSEvent *)theEvent -{ - [super mouseMoved:theEvent]; -} - -@end - -@implementation QTMovie (HBQTMovieExtensions) - -- (BOOL)isPlaying -{ - if (self.rate > 0) - { - return YES; - } - else - { - return NO; - } -} - -- (NSString *)timecode -{ - QTTime time = [self currentTime]; - double timeInSeconds = (double)time.timeValue / time.timeScale; - UInt16 seconds = (UInt16)fmod(timeInSeconds, 60.0); - UInt16 minutes = (UInt16)fmod(timeInSeconds / 60.0, 60.0); - UInt16 hours = (UInt16)(timeInSeconds / (60.0 * 60.0)); - UInt16 milliseconds = (UInt16)(timeInSeconds - (int) timeInSeconds) * 1000; - return [NSString stringWithFormat:@"%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds]; -} - -- (void)setCurrentTimeDouble:(double)value -{ - long timeScale = [[self attributeForKey:QTMovieTimeScaleAttribute] longValue]; - [self setCurrentTime:QTMakeTime((long long)value * timeScale, timeScale)]; -} - -@end
\ No newline at end of file diff --git a/macosx/icons/next-p.pdf b/macosx/icons/NextTemplate.pdf Binary files differindex bb11381cd..bb11381cd 100644 --- a/macosx/icons/next-p.pdf +++ b/macosx/icons/NextTemplate.pdf diff --git a/macosx/icons/pause-p.pdf b/macosx/icons/PauseTemplate.pdf Binary files differindex 6a6224a75..6a6224a75 100644 --- a/macosx/icons/pause-p.pdf +++ b/macosx/icons/PauseTemplate.pdf diff --git a/macosx/icons/play-p.pdf b/macosx/icons/PlayTemplate.pdf Binary files differindex 028feb270..028feb270 100644 --- a/macosx/icons/play-p.pdf +++ b/macosx/icons/PlayTemplate.pdf diff --git a/macosx/icons/prev-p.pdf b/macosx/icons/PrevTemplate.pdf Binary files differindex 7f37c9af7..7f37c9af7 100644 --- a/macosx/icons/prev-p.pdf +++ b/macosx/icons/PrevTemplate.pdf diff --git a/macosx/icons/volHighTemplate.pdf b/macosx/icons/volHighTemplate.pdf Binary files differnew file mode 100644 index 000000000..ee1f4e674 --- /dev/null +++ b/macosx/icons/volHighTemplate.pdf diff --git a/macosx/icons/volLowTemplate.pdf b/macosx/icons/volLowTemplate.pdf Binary files differnew file mode 100644 index 000000000..8a9b97274 --- /dev/null +++ b/macosx/icons/volLowTemplate.pdf |