diff --git a/AVFoundationSimplePlayer/Common/ElephantSeals.mov b/AVFoundationSimplePlayer/Common/ElephantSeals.mov new file mode 100644 index 00000000..69641555 Binary files /dev/null and b/AVFoundationSimplePlayer/Common/ElephantSeals.mov differ diff --git a/AVFoundationSimplePlayer/LICENSE.txt b/AVFoundationSimplePlayer/LICENSE.txt new file mode 100644 index 00000000..4e443d77 --- /dev/null +++ b/AVFoundationSimplePlayer/LICENSE.txt @@ -0,0 +1,42 @@ +Sample code project: AVFoundationSimplePlayer-iOS: Using AVFoundation to Play Media +Version: 2.0 + +IMPORTANT: This Apple software is supplied to you by Apple +Inc. ("Apple") in consideration of your agreement to the following +terms, and your use, installation, modification or redistribution of +this Apple software constitutes acceptance of these terms. If you do +not agree with these terms, please do not use, install, modify or +redistribute this Apple software. + +In consideration of your agreement to abide by the following terms, and +subject to these terms, Apple grants you a personal, non-exclusive +license, under Apple's copyrights in this original Apple software (the +"Apple Software"), to use, reproduce, modify and redistribute the Apple +Software, with or without modifications, in source and/or binary forms; +provided that if you redistribute the Apple Software in its entirety and +without modifications, you must retain this notice and the following +text and disclaimers in all such redistributions of the Apple Software. +Neither the name, trademarks, service marks or logos of Apple Inc. may +be used to endorse or promote products derived from the Apple Software +without specific prior written permission from Apple. Except as +expressly stated in this notice, no other rights or licenses, express or +implied, are granted by Apple herein, including but not limited to any +patent rights that may be infringed by your derivative works or by other +works in which the Apple Software may be incorporated. + +The Apple Software is provided by Apple on an "AS IS" basis. APPLE +MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + +IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +Copyright (C) 2016 Apple Inc. All Rights Reserved. diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS.xcodeproj/project.pbxproj b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS.xcodeproj/project.pbxproj new file mode 100644 index 00000000..2f9af4ff --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS.xcodeproj/project.pbxproj @@ -0,0 +1,330 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + D2010EFB1B0E8A8D00BD0BDF /* ElephantSeals.mov in Resources */ = {isa = PBXBuildFile; fileRef = D2010EFA1B0E8A8D00BD0BDF /* ElephantSeals.mov */; }; + D20EB3C21AF94FDE0059CD72 /* AAPLPlayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D20EB3C11AF94FDE0059CD72 /* AAPLPlayerViewController.m */; }; + D2385B4B1AF5181400DC8ADE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2385B491AF5181400DC8ADE /* Main.storyboard */; }; + D2385B4D1AF5181400DC8ADE /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D2385B4C1AF5181400DC8ADE /* Images.xcassets */; }; + D23E499F1B0D30AF0013F574 /* AAPLAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D23E499E1B0D30AF0013F574 /* AAPLAppDelegate.m */; }; + D27489A01B1CD92F0020D82A /* AAPLPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = D274899F1B1CD92F0020D82A /* AAPLPlayerView.m */; }; + D27489A31B1CD9370020D82A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D27489A11B1CD9370020D82A /* Localizable.strings */; }; + D2C148E51B0FA7AD004F41DA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D2C148E41B0FA7AD004F41DA /* main.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + D2010EFA1B0E8A8D00BD0BDF /* ElephantSeals.mov */ = {isa = PBXFileReference; lastKnownFileType = video.quicktime; name = ElephantSeals.mov; path = ../Common/ElephantSeals.mov; sourceTree = SOURCE_ROOT; }; + D20EB3C11AF94FDE0059CD72 /* AAPLPlayerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AAPLPlayerViewController.m; sourceTree = ""; }; + D2385B421AF5181400DC8ADE /* AVFoundationSimplePlayer-ObjC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AVFoundationSimplePlayer-ObjC.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + D2385B4A1AF5181400DC8ADE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + D2385B4C1AF5181400DC8ADE /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D2385B511AF5181400DC8ADE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D23E499D1B0D2DF90013F574 /* AAPLAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AAPLAppDelegate.h; sourceTree = ""; }; + D23E499E1B0D30AF0013F574 /* AAPLAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AAPLAppDelegate.m; sourceTree = ""; }; + D23E49A01B0D325D0013F574 /* AAPLPlayerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AAPLPlayerViewController.h; sourceTree = ""; }; + D274899E1B1CD92F0020D82A /* AAPLPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AAPLPlayerView.h; sourceTree = ""; }; + D274899F1B1CD92F0020D82A /* AAPLPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AAPLPlayerView.m; sourceTree = ""; }; + D27489A21B1CD9370020D82A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + D2C148E41B0FA7AD004F41DA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + D2CF22161B1E5EAD0006C864 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D2385B3F1AF5181400DC8ADE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D2010EFD1B0E8AD600BD0BDF /* Common */ = { + isa = PBXGroup; + children = ( + D2010EFA1B0E8A8D00BD0BDF /* ElephantSeals.mov */, + ); + name = Common; + sourceTree = ""; + }; + D2385B391AF5181400DC8ADE = { + isa = PBXGroup; + children = ( + D2CF22161B1E5EAD0006C864 /* README.md */, + D2385B441AF5181400DC8ADE /* AVFoundationSimplePlayer-iOS */, + D2385B431AF5181400DC8ADE /* Products */, + ); + sourceTree = ""; + }; + D2385B431AF5181400DC8ADE /* Products */ = { + isa = PBXGroup; + children = ( + D2385B421AF5181400DC8ADE /* AVFoundationSimplePlayer-ObjC.app */, + ); + name = Products; + sourceTree = ""; + }; + D2385B441AF5181400DC8ADE /* AVFoundationSimplePlayer-iOS */ = { + isa = PBXGroup; + children = ( + D23E499E1B0D30AF0013F574 /* AAPLAppDelegate.m */, + D23E499D1B0D2DF90013F574 /* AAPLAppDelegate.h */, + D20EB3C11AF94FDE0059CD72 /* AAPLPlayerViewController.m */, + D23E49A01B0D325D0013F574 /* AAPLPlayerViewController.h */, + D274899E1B1CD92F0020D82A /* AAPLPlayerView.h */, + D274899F1B1CD92F0020D82A /* AAPLPlayerView.m */, + D2385B491AF5181400DC8ADE /* Main.storyboard */, + D2385B4C1AF5181400DC8ADE /* Images.xcassets */, + D2385B511AF5181400DC8ADE /* Info.plist */, + D27489A11B1CD9370020D82A /* Localizable.strings */, + D2C148E31B0FA76B004F41DA /* Supporting Files */, + D2010EFD1B0E8AD600BD0BDF /* Common */, + ); + path = "AVFoundationSimplePlayer-iOS"; + sourceTree = ""; + }; + D2C148E31B0FA76B004F41DA /* Supporting Files */ = { + isa = PBXGroup; + children = ( + D2C148E41B0FA7AD004F41DA /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + D2385B411AF5181400DC8ADE /* AVFoundationSimplePlayer-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = D2385B5F1AF5181400DC8ADE /* Build configuration list for PBXNativeTarget "AVFoundationSimplePlayer-iOS" */; + buildPhases = ( + D2385B3E1AF5181400DC8ADE /* Sources */, + D2385B3F1AF5181400DC8ADE /* Frameworks */, + D2385B401AF5181400DC8ADE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "AVFoundationSimplePlayer-iOS"; + productName = "AVFoundationSimplePlayer-iOS"; + productReference = D2385B421AF5181400DC8ADE /* AVFoundationSimplePlayer-ObjC.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D2385B3A1AF5181400DC8ADE /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0700; + ORGANIZATIONNAME = "Apple Inc."; + TargetAttributes = { + D2385B411AF5181400DC8ADE = { + CreatedOnToolsVersion = 7.0; + }; + }; + }; + buildConfigurationList = D2385B3D1AF5181400DC8ADE /* Build configuration list for PBXProject "AVFoundationSimplePlayer-iOS" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D2385B391AF5181400DC8ADE; + productRefGroup = D2385B431AF5181400DC8ADE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2385B411AF5181400DC8ADE /* AVFoundationSimplePlayer-iOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D2385B401AF5181400DC8ADE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D27489A31B1CD9370020D82A /* Localizable.strings in Resources */, + D2385B4B1AF5181400DC8ADE /* Main.storyboard in Resources */, + D2385B4D1AF5181400DC8ADE /* Images.xcassets in Resources */, + D2010EFB1B0E8A8D00BD0BDF /* ElephantSeals.mov in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D2385B3E1AF5181400DC8ADE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D23E499F1B0D30AF0013F574 /* AAPLAppDelegate.m in Sources */, + D27489A01B1CD92F0020D82A /* AAPLPlayerView.m in Sources */, + D2C148E51B0FA7AD004F41DA /* main.m in Sources */, + D20EB3C21AF94FDE0059CD72 /* AAPLPlayerViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + D2385B491AF5181400DC8ADE /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + D2385B4A1AF5181400DC8ADE /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + D27489A11B1CD9370020D82A /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + D27489A21B1CD9370020D82A /* en */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + D2385B5D1AF5181400DC8ADE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D2385B5E1AF5181400DC8ADE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + D2385B601AF5181400DC8ADE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = "AVFoundationSimplePlayer-iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "AVFoundationSimplePlayer-ObjC"; + }; + name = Debug; + }; + D2385B611AF5181400DC8ADE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = "AVFoundationSimplePlayer-iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "AVFoundationSimplePlayer-ObjC"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D2385B3D1AF5181400DC8ADE /* Build configuration list for PBXProject "AVFoundationSimplePlayer-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D2385B5D1AF5181400DC8ADE /* Debug */, + D2385B5E1AF5181400DC8ADE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D2385B5F1AF5181400DC8ADE /* Build configuration list for PBXNativeTarget "AVFoundationSimplePlayer-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D2385B601AF5181400DC8ADE /* Debug */, + D2385B611AF5181400DC8ADE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D2385B3A1AF5181400DC8ADE /* Project object */; +} diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLAppDelegate.h b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLAppDelegate.h new file mode 100644 index 00000000..e5e2e991 --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLAppDelegate.h @@ -0,0 +1,17 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + Application delegate. +*/ + +@import UIKit; + + +@interface AAPLAppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end + diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLAppDelegate.m b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLAppDelegate.m new file mode 100644 index 00000000..1e4f2f80 --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLAppDelegate.m @@ -0,0 +1,13 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + Application delegate. +*/ + +#import "AAPLAppDelegate.h" + + +@implementation AAPLAppDelegate +@end diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLPlayerView.h b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLPlayerView.h new file mode 100644 index 00000000..a94b0c91 --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLPlayerView.h @@ -0,0 +1,16 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + View containing an AVPlayerLayer. +*/ + +@import UIKit; + +@class AVPlayer; + +@interface AAPLPlayerView : UIView +@property AVPlayer *player; +@property (readonly) AVPlayerLayer *playerLayer; +@end diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLPlayerView.m b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLPlayerView.m new file mode 100644 index 00000000..cfa02265 --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLPlayerView.m @@ -0,0 +1,34 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + View containing an AVPlayerLayer. +*/ + +@import Foundation; +@import AVFoundation; +#import "AAPLPlayerView.h" + + +@implementation AAPLPlayerView + +- (AVPlayer *)player { + return self.playerLayer.player; +} + +- (void)setPlayer:(AVPlayer *)player { + self.playerLayer.player = player; +} + +// override UIView ++ (Class)layerClass { + return [AVPlayerLayer class]; +} + +- (AVPlayerLayer *)playerLayer { + return (AVPlayerLayer *)self.layer; +} + +@end + diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLPlayerViewController.h b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLPlayerViewController.h new file mode 100644 index 00000000..91cff872 --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLPlayerViewController.h @@ -0,0 +1,32 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + View controller containing a player view and basic playback controls. +*/ + +@import UIKit; + + +@class AAPLPlayerView; + +@interface AAPLPlayerViewController : UIViewController + +@property (readonly) AVPlayer *player; +@property AVURLAsset *asset; + +@property CMTime currentTime; +@property (readonly) CMTime duration; +@property float rate; + +@property (weak) IBOutlet UISlider *timeSlider; +@property (weak) IBOutlet UILabel *startTimeLabel; +@property (weak) IBOutlet UILabel *durationLabel; +@property (weak) IBOutlet UIButton *rewindButton; +@property (weak) IBOutlet UIButton *playPauseButton; +@property (weak) IBOutlet UIButton *fastForwardButton; +@property (weak) IBOutlet AAPLPlayerView *playerView; + +@end + diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLPlayerViewController.m b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLPlayerViewController.m new file mode 100644 index 00000000..881149c5 --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/AAPLPlayerViewController.m @@ -0,0 +1,316 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + View controller containing a player view and basic playback controls. +*/ + + +@import Foundation; +@import AVFoundation; +@import CoreMedia.CMTime; +#import "AAPLPlayerViewController.h" +#import "AAPLPlayerView.h" + + +// Private properties +@interface AAPLPlayerViewController () +{ + AVPlayer *_player; + AVURLAsset *_asset; + id _timeObserverToken; + AVPlayerItem *_playerItem; +} + +@property AVPlayerItem *playerItem; + +@property (readonly) AVPlayerLayer *playerLayer; + +@end + +@implementation AAPLPlayerViewController + +// MARK: - View Handling + +/* + KVO context used to differentiate KVO callbacks for this class versus other + classes in its class hierarchy. +*/ +static int AAPLPlayerViewControllerKVOContext = 0; + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + /* + Update the UI when these player properties change. + + Use the context parameter to distinguish KVO for our particular observers and not + those destined for a subclass that also happens to be observing these properties. + */ + [self addObserver:self forKeyPath:@"asset" options:NSKeyValueObservingOptionNew context:&AAPLPlayerViewControllerKVOContext]; + [self addObserver:self forKeyPath:@"player.currentItem.duration" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:&AAPLPlayerViewControllerKVOContext]; + [self addObserver:self forKeyPath:@"player.rate" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:&AAPLPlayerViewControllerKVOContext]; + [self addObserver:self forKeyPath:@"player.currentItem.status" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:&AAPLPlayerViewControllerKVOContext]; + + self.playerView.playerLayer.player = self.player; + + NSURL *movieURL = [[NSBundle mainBundle] URLForResource:@"ElephantSeals" withExtension:@"mov"]; + self.asset = [AVURLAsset assetWithURL:movieURL]; + + // Use a weak self variable to avoid a retain cycle in the block. + AAPLPlayerViewController __weak *weakSelf = self; + _timeObserverToken = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock: + ^(CMTime time) { + weakSelf.timeSlider.value = CMTimeGetSeconds(time); + }]; +} + +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; + + if (_timeObserverToken) { + [self.player removeTimeObserver:_timeObserverToken]; + _timeObserverToken = nil; + } + + [self.player pause]; + + [self removeObserver:self forKeyPath:@"asset" context:&AAPLPlayerViewControllerKVOContext]; + [self removeObserver:self forKeyPath:@"player.currentItem.duration" context:&AAPLPlayerViewControllerKVOContext]; + [self removeObserver:self forKeyPath:@"player.rate" context:&AAPLPlayerViewControllerKVOContext]; + [self removeObserver:self forKeyPath:@"player.currentItem.status" context:&AAPLPlayerViewControllerKVOContext]; +} + +// MARK: - Properties + +// Will attempt load and test these asset keys before playing ++ (NSArray *)assetKeysRequiredToPlay { + return @[ @"playable", @"hasProtectedContent" ]; +} + +- (AVPlayer *)player { + if (!_player) + _player = [[AVPlayer alloc] init]; + return _player; +} + +- (CMTime)currentTime { + return self.player.currentTime; +} +- (void)setCurrentTime:(CMTime)newCurrentTime { + [self.player seekToTime:newCurrentTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; +} + +- (CMTime)duration { + return self.player.currentItem ? self.player.currentItem.duration : kCMTimeZero; +} + +- (float)rate { + return self.player.rate; +} +- (void)setRate:(float)newRate { + self.player.rate = newRate; +} + +- (AVPlayerLayer *)playerLayer { + return self.playerView.playerLayer; +} + +- (AVPlayerItem *)playerItem { + return _playerItem; +} + +- (void)setPlayerItem:(AVPlayerItem *)newPlayerItem { + if (_playerItem != newPlayerItem) { + + _playerItem = newPlayerItem; + + // If needed, configure player item here before associating it with a player + // (example: adding outputs, setting text style rules, selecting media options) + [self.player replaceCurrentItemWithPlayerItem:_playerItem]; + } +} + +// MARK: - Asset Loading + +- (void)asynchronouslyLoadURLAsset:(AVURLAsset *)newAsset { + + /* + Using AVAsset now runs the risk of blocking the current thread + (the main UI thread) whilst I/O happens to populate the + properties. It's prudent to defer our work until the properties + we need have been loaded. + */ + [newAsset loadValuesAsynchronouslyForKeys:AAPLPlayerViewController.assetKeysRequiredToPlay completionHandler:^{ + + /* + The asset invokes its completion handler on an arbitrary queue. + To avoid multiple threads using our internal state at the same time + we'll elect to use the main thread at all times, let's dispatch + our handler to the main queue. + */ + dispatch_async(dispatch_get_main_queue(), ^{ + + if (newAsset != self.asset) { + /* + self.asset has already changed! No point continuing because + another newAsset will come along in a moment. + */ + return; + } + + /* + Test whether the values of each of the keys we need have been + successfully loaded. + */ + for (NSString *key in self.class.assetKeysRequiredToPlay) { + NSError *error = nil; + if ([newAsset statusOfValueForKey:key error:&error] == AVKeyValueStatusFailed) { + + NSString *message = [NSString localizedStringWithFormat:NSLocalizedString(@"error.asset_key_%@_failed.description", @"Can't use this AVAsset because one of it's keys failed to load"), key]; + + [self handleErrorWithMessage:message error:error]; + + return; + } + } + + // We can't play this asset. + if (!newAsset.playable || newAsset.hasProtectedContent) { + NSString *message = NSLocalizedString(@"error.asset_not_playable.description", @"Can't use this AVAsset because it isn't playable or has protected content"); + + [self handleErrorWithMessage:message error:nil]; + + return; + } + + /* + We can play this asset. Create a new AVPlayerItem and make it + our player's current item. + */ + self.playerItem = [AVPlayerItem playerItemWithAsset:newAsset]; + }); + }]; +} + +// MARK: - IBActions + +- (IBAction)playPauseButtonWasPressed:(UIButton *)sender { + if (self.player.rate != 1.0) { + // not playing foward so play + if (CMTIME_COMPARE_INLINE(self.currentTime, ==, self.duration)) { + // at end so got back to begining + self.currentTime = kCMTimeZero; + } + [self.player play]; + } else { + // playing so pause + [self.player pause]; + } +} + +- (IBAction)rewindButtonWasPressed:(UIButton *)sender { + self.rate = MAX(self.player.rate - 2.0, -2.0); // rewind no faster than -2.0 +} + +- (IBAction)fastForwardButtonWasPressed:(UIButton *)sender { + self.rate = MIN(self.player.rate + 2.0, 2.0); // fast forward no faster than 2.0 +} + +- (IBAction)timeSliderDidChange:(UISlider *)sender { + self.currentTime = CMTimeMakeWithSeconds(sender.value, 1000); +} + +// MARK: - KV Observation + +// Update our UI when player or player.currentItem changes +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + + if (context != &AAPLPlayerViewControllerKVOContext) { + // KVO isn't for us. + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + return; + } + + if ([keyPath isEqualToString:@"asset"]) { + if (self.asset) { + [self asynchronouslyLoadURLAsset:self.asset]; + } + } + else if ([keyPath isEqualToString:@"player.currentItem.duration"]) { + + // Update timeSlider and enable/disable controls when duration > 0.0 + + // Handle NSNull value for NSKeyValueChangeNewKey, i.e. when player.currentItem is nil + NSValue *newDurationAsValue = change[NSKeyValueChangeNewKey]; + CMTime newDuration = [newDurationAsValue isKindOfClass:[NSValue class]] ? newDurationAsValue.CMTimeValue : kCMTimeZero; + BOOL hasValidDuration = CMTIME_IS_NUMERIC(newDuration) && newDuration.value != 0; + double newDurationSeconds = hasValidDuration ? CMTimeGetSeconds(newDuration) : 0.0; + + self.timeSlider.maximumValue = newDurationSeconds; + self.timeSlider.value = hasValidDuration ? CMTimeGetSeconds(self.currentTime) : 0.0; + self.rewindButton.enabled = hasValidDuration; + self.playPauseButton.enabled = hasValidDuration; + self.fastForwardButton.enabled = hasValidDuration; + self.timeSlider.enabled = hasValidDuration; + self.startTimeLabel.enabled = hasValidDuration; + self.durationLabel.enabled = hasValidDuration; + int wholeMinutes = (int)trunc(newDurationSeconds / 60); + self.durationLabel.text = [NSString stringWithFormat:@"%d:%02d", wholeMinutes, (int)trunc(newDurationSeconds) - wholeMinutes * 60]; + + } + else if ([keyPath isEqualToString:@"player.rate"]) { + // Update playPauseButton image + + double newRate = [change[NSKeyValueChangeNewKey] doubleValue]; + UIImage *buttonImage = (newRate == 1.0) ? [UIImage imageNamed:@"PauseButton"] : [UIImage imageNamed:@"PlayButton"]; + [self.playPauseButton setImage:buttonImage forState:UIControlStateNormal]; + + } + else if ([keyPath isEqualToString:@"player.currentItem.status"]) { + // Display an error if status becomes Failed + + // Handle NSNull value for NSKeyValueChangeNewKey, i.e. when player.currentItem is nil + NSNumber *newStatusAsNumber = change[NSKeyValueChangeNewKey]; + AVPlayerItemStatus newStatus = [newStatusAsNumber isKindOfClass:[NSNumber class]] ? newStatusAsNumber.integerValue : AVPlayerItemStatusUnknown; + + if (newStatus == AVPlayerItemStatusFailed) { + [self handleErrorWithMessage:self.player.currentItem.error.localizedDescription error:self.player.currentItem.error]; + } + + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +// Trigger KVO for anyone observing our properties affected by player and player.currentItem ++ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { + if ([key isEqualToString:@"duration"]) { + return [NSSet setWithArray:@[ @"player.currentItem.duration" ]]; + } else if ([key isEqualToString:@"currentTime"]) { + return [NSSet setWithArray:@[ @"player.currentItem.currentTime" ]]; + } else if ([key isEqualToString:@"rate"]) { + return [NSSet setWithArray:@[ @"player.rate" ]]; + } else { + return [super keyPathsForValuesAffectingValueForKey:key]; + } +} + +// MARK: - Error Handling + +- (void)handleErrorWithMessage:(NSString *)message error:(NSError *)error { + NSLog(@"Error occured with message: %@, error: %@.", message, error); + + NSString *alertTitle = NSLocalizedString(@"alert.error.title", @"Alert title for errors"); + NSString *defaultAlertMesssage = NSLocalizedString(@"error.default.description", @"Default error message when no NSError provided"); + UIAlertController *controller = [UIAlertController alertControllerWithTitle:alertTitle message:message ?: defaultAlertMesssage preferredStyle:UIAlertControllerStyleAlert]; + + NSString *alertActionTitle = NSLocalizedString(@"alert.error.actions.OK", @"OK on error alert"); + UIAlertAction *action = [UIAlertAction actionWithTitle:alertActionTitle style:UIAlertActionStyleDefault handler:nil]; + [controller addAction:action]; + + [self presentViewController:controller animated:YES completion:nil]; +} + +@end diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Base.lproj/Main.storyboard b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Base.lproj/Main.storyboard new file mode 100644 index 00000000..33f0282f --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Base.lproj/Main.storyboard @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/AppIcon.appiconset/Contents.json b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..36d2c80d --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/Contents.json b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/Contents.json new file mode 100644 index 00000000..01a9d605 --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "PauseButton.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "PauseButton@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "PauseButton@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton.png b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton.png new file mode 100644 index 00000000..b812da3e Binary files /dev/null and b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton.png differ diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@2x.png b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@2x.png new file mode 100644 index 00000000..5fd2dacf Binary files /dev/null and b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@2x.png differ diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@3x.png b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@3x.png new file mode 100644 index 00000000..f1664dc6 Binary files /dev/null and b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@3x.png differ diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/Contents.json b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/Contents.json new file mode 100644 index 00000000..bdde07ab --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "PlayButton.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "PlayButton@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "PlayButton@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton.png b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton.png new file mode 100644 index 00000000..1c9975a5 Binary files /dev/null and b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton.png differ diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@2x.png b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@2x.png new file mode 100644 index 00000000..9aa4bf37 Binary files /dev/null and b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@2x.png differ diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@3x.png b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@3x.png new file mode 100644 index 00000000..31046b7d Binary files /dev/null and b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@3x.png differ diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/Contents.json b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/Contents.json new file mode 100644 index 00000000..52413004 --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ScanBackwardButton.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ScanBackwardButton@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ScanBackwardButton@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton.png b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton.png new file mode 100644 index 00000000..6fbe0cd3 Binary files /dev/null and b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton.png differ diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@2x.png b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@2x.png new file mode 100644 index 00000000..46fd14ff Binary files /dev/null and b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@2x.png differ diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@3x.png b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@3x.png new file mode 100644 index 00000000..b1cca0f1 Binary files /dev/null and b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@3x.png differ diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/Contents.json b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/Contents.json new file mode 100644 index 00000000..8f07e000 --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ScanForwardButton.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ScanForwardButton@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ScanForwardButton@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton.png b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton.png new file mode 100644 index 00000000..43e251ec Binary files /dev/null and b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton.png differ diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@2x.png b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@2x.png new file mode 100644 index 00000000..bf28ce79 Binary files /dev/null and b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@2x.png differ diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@3x.png b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@3x.png new file mode 100644 index 00000000..f8ccaf93 Binary files /dev/null and b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@3x.png differ diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Info.plist b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Info.plist new file mode 100644 index 00000000..43c0659b --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + Main + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/en.lproj/Localizable.strings b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/en.lproj/Localizable.strings new file mode 100644 index 00000000..6df6625d --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/en.lproj/Localizable.strings @@ -0,0 +1,22 @@ +/* + + + Localizable strings. + + +*/ + +// Alert title for errors. +"alert.error.title" = "Error"; + +// OK action on error alert +"alert.error.actions.OK" = "OK on error alert"; + +// Can't use this AVAsset because one of it's keys failed to load. +"error.asset_key_%@_failed.description" = "Media failed to load key \"%@\""; + +// Can't use this AVAsset because it isn't playable or has protected content. +"error.asset_not_playable.description" = "Media isn't playable or has protected content"; + +// Default error message when no NSError provided. +"error.default.description" = "Unknown error"; diff --git a/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/main.m b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/main.m new file mode 100644 index 00000000..54cd040e --- /dev/null +++ b/AVFoundationSimplePlayer/Objective-C/AVFoundationSimplePlayer-iOS/main.m @@ -0,0 +1,59 @@ +/* + File: AAPLAppDelegate.m + + Abstract: Sample code for AVFoundationSimplePlayer-ObjC-iOS + + Version: 1.0 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by + Apple Inc. ("Apple") in consideration of your agreement to the + following terms, and your use, installation, modification or + redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, + install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. + may be used to endorse or promote products derived from the Apple + Software without specific prior written permission from Apple. Except + as expressly stated in this notice, no other rights or licenses, express + or implied, are granted by Apple herein, including but not limited to + any patent rights that may be infringed by your derivative works or by + other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2013-2015 Apple Inc. All Rights Reserved. +*/ + +#import +#import "AAPLAppDelegate.h" + + + + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AAPLAppDelegate class])); + } +} diff --git a/AVFoundationSimplePlayer/README.md b/AVFoundationSimplePlayer/README.md new file mode 100644 index 00000000..cee6bf3d --- /dev/null +++ b/AVFoundationSimplePlayer/README.md @@ -0,0 +1,15 @@ +# AVFoundationSimplePlayer-iOS: Using AVFoundation to Play Media + +Demonstrates how to create a simple movie playback app using only the AVPlayer and AVPlayerLayer classes from AVFoundation (not AVKit). You'll see how to load a movie file as an AVAsset and then how to implement various functionality including play, pause, fast forward, rewind, time slider updating, and scrubbing. + +## Requirements + +### Build + +Xcode 8.0, iOS 9.0 SDK + +### Runtime + +iOS 9.0 or later + +Copyright (C) 2016 Apple Inc. All rights reserved. diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS.xcodeproj/project.pbxproj b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS.xcodeproj/project.pbxproj new file mode 100644 index 00000000..04bf4dc2 --- /dev/null +++ b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS.xcodeproj/project.pbxproj @@ -0,0 +1,322 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + D2010EF91B0E8A8800BD0BDF /* ElephantSeals.mov in Resources */ = {isa = PBXBuildFile; fileRef = D2010EF81B0E8A8800BD0BDF /* ElephantSeals.mov */; }; + D20EB3C21AF94FDE0059CD72 /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20EB3C11AF94FDE0059CD72 /* PlayerViewController.swift */; }; + D2385B461AF5181400DC8ADE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2385B451AF5181400DC8ADE /* AppDelegate.swift */; }; + D2385B4B1AF5181400DC8ADE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2385B491AF5181400DC8ADE /* Main.storyboard */; }; + D2385B4D1AF5181400DC8ADE /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D2385B4C1AF5181400DC8ADE /* Images.xcassets */; }; + D27489971B1CD4E00020D82A /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27489961B1CD4E00020D82A /* PlayerView.swift */; }; + D274899C1B1CD4EB0020D82A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D27489981B1CD4EB0020D82A /* Localizable.strings */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + D2010EF81B0E8A8800BD0BDF /* ElephantSeals.mov */ = {isa = PBXFileReference; lastKnownFileType = video.quicktime; name = ElephantSeals.mov; path = ../Common/ElephantSeals.mov; sourceTree = SOURCE_ROOT; }; + D20EB3C11AF94FDE0059CD72 /* PlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerViewController.swift; sourceTree = ""; }; + D2385B421AF5181400DC8ADE /* AVFoundationSimplePlayer-Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AVFoundationSimplePlayer-Swift.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + D2385B451AF5181400DC8ADE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + D2385B4A1AF5181400DC8ADE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + D2385B4C1AF5181400DC8ADE /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D2385B511AF5181400DC8ADE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D27489961B1CD4E00020D82A /* PlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; + D27489991B1CD4EB0020D82A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + D2CF22151B1E5E970006C864 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D2385B3F1AF5181400DC8ADE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D2010EFC1B0E8ACB00BD0BDF /* Common */ = { + isa = PBXGroup; + children = ( + D2010EF81B0E8A8800BD0BDF /* ElephantSeals.mov */, + ); + name = Common; + sourceTree = ""; + }; + D2385B391AF5181400DC8ADE = { + isa = PBXGroup; + children = ( + D2CF22151B1E5E970006C864 /* README.md */, + D2385B441AF5181400DC8ADE /* AVFoundationSimplePlayer-iOS */, + D2385B431AF5181400DC8ADE /* Products */, + ); + sourceTree = ""; + }; + D2385B431AF5181400DC8ADE /* Products */ = { + isa = PBXGroup; + children = ( + D2385B421AF5181400DC8ADE /* AVFoundationSimplePlayer-Swift.app */, + ); + name = Products; + sourceTree = ""; + }; + D2385B441AF5181400DC8ADE /* AVFoundationSimplePlayer-iOS */ = { + isa = PBXGroup; + children = ( + D2385B451AF5181400DC8ADE /* AppDelegate.swift */, + D20EB3C11AF94FDE0059CD72 /* PlayerViewController.swift */, + D27489961B1CD4E00020D82A /* PlayerView.swift */, + D2385B491AF5181400DC8ADE /* Main.storyboard */, + D2385B4C1AF5181400DC8ADE /* Images.xcassets */, + D2385B511AF5181400DC8ADE /* Info.plist */, + D27489981B1CD4EB0020D82A /* Localizable.strings */, + D2010EFC1B0E8ACB00BD0BDF /* Common */, + ); + path = "AVFoundationSimplePlayer-iOS"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + D2385B411AF5181400DC8ADE /* AVFoundationSimplePlayer-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = D2385B5F1AF5181400DC8ADE /* Build configuration list for PBXNativeTarget "AVFoundationSimplePlayer-iOS" */; + buildPhases = ( + D2385B3E1AF5181400DC8ADE /* Sources */, + D2385B3F1AF5181400DC8ADE /* Frameworks */, + D2385B401AF5181400DC8ADE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "AVFoundationSimplePlayer-iOS"; + productName = "AVFoundationSimplePlayer-iOS"; + productReference = D2385B421AF5181400DC8ADE /* AVFoundationSimplePlayer-Swift.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D2385B3A1AF5181400DC8ADE /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = "Apple Inc."; + TargetAttributes = { + D2385B411AF5181400DC8ADE = { + CreatedOnToolsVersion = 7.0; + LastSwiftMigration = 0800; + }; + }; + }; + buildConfigurationList = D2385B3D1AF5181400DC8ADE /* Build configuration list for PBXProject "AVFoundationSimplePlayer-iOS" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D2385B391AF5181400DC8ADE; + productRefGroup = D2385B431AF5181400DC8ADE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2385B411AF5181400DC8ADE /* AVFoundationSimplePlayer-iOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D2385B401AF5181400DC8ADE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D274899C1B1CD4EB0020D82A /* Localizable.strings in Resources */, + D2010EF91B0E8A8800BD0BDF /* ElephantSeals.mov in Resources */, + D2385B4B1AF5181400DC8ADE /* Main.storyboard in Resources */, + D2385B4D1AF5181400DC8ADE /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D2385B3E1AF5181400DC8ADE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D27489971B1CD4E00020D82A /* PlayerView.swift in Sources */, + D2385B461AF5181400DC8ADE /* AppDelegate.swift in Sources */, + D20EB3C21AF94FDE0059CD72 /* PlayerViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + D2385B491AF5181400DC8ADE /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + D2385B4A1AF5181400DC8ADE /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + D27489981B1CD4EB0020D82A /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + D27489991B1CD4EB0020D82A /* en */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + D2385B5D1AF5181400DC8ADE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D2385B5E1AF5181400DC8ADE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + D2385B601AF5181400DC8ADE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "AVFoundationSimplePlayer-iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "AVFoundationSimplePlayer-Swift"; + PROVISIONING_PROFILE = ""; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + D2385B611AF5181400DC8ADE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "AVFoundationSimplePlayer-iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "AVFoundationSimplePlayer-Swift"; + PROVISIONING_PROFILE = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D2385B3D1AF5181400DC8ADE /* Build configuration list for PBXProject "AVFoundationSimplePlayer-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D2385B5D1AF5181400DC8ADE /* Debug */, + D2385B5E1AF5181400DC8ADE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D2385B5F1AF5181400DC8ADE /* Build configuration list for PBXNativeTarget "AVFoundationSimplePlayer-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D2385B601AF5181400DC8ADE /* Debug */, + D2385B611AF5181400DC8ADE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D2385B3A1AF5181400DC8ADE /* Project object */; +} diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/AppDelegate.swift b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/AppDelegate.swift new file mode 100644 index 00000000..de287ffa --- /dev/null +++ b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/AppDelegate.swift @@ -0,0 +1,16 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + Main application entry point. +*/ + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + // MARK: Properties + + var window: UIWindow? +} diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Base.lproj/Main.storyboard b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Base.lproj/Main.storyboard new file mode 100644 index 00000000..75bf12a8 --- /dev/null +++ b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Base.lproj/Main.storyboard @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/AppIcon.appiconset/Contents.json b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..36d2c80d --- /dev/null +++ b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/Contents.json b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/Contents.json new file mode 100644 index 00000000..01a9d605 --- /dev/null +++ b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "PauseButton.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "PauseButton@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "PauseButton@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton.png b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton.png new file mode 100644 index 00000000..b812da3e Binary files /dev/null and b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton.png differ diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@2x.png b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@2x.png new file mode 100644 index 00000000..5fd2dacf Binary files /dev/null and b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@2x.png differ diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@3x.png b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@3x.png new file mode 100644 index 00000000..f1664dc6 Binary files /dev/null and b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PauseButton.imageset/PauseButton@3x.png differ diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/Contents.json b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/Contents.json new file mode 100644 index 00000000..bdde07ab --- /dev/null +++ b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "PlayButton.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "PlayButton@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "PlayButton@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton.png b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton.png new file mode 100644 index 00000000..1c9975a5 Binary files /dev/null and b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton.png differ diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@2x.png b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@2x.png new file mode 100644 index 00000000..9aa4bf37 Binary files /dev/null and b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@2x.png differ diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@3x.png b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@3x.png new file mode 100644 index 00000000..31046b7d Binary files /dev/null and b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/PlayButton.imageset/PlayButton@3x.png differ diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/Contents.json b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/Contents.json new file mode 100644 index 00000000..52413004 --- /dev/null +++ b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ScanBackwardButton.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ScanBackwardButton@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ScanBackwardButton@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton.png b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton.png new file mode 100644 index 00000000..6fbe0cd3 Binary files /dev/null and b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton.png differ diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@2x.png b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@2x.png new file mode 100644 index 00000000..46fd14ff Binary files /dev/null and b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@2x.png differ diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@3x.png b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@3x.png new file mode 100644 index 00000000..b1cca0f1 Binary files /dev/null and b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanBackwardButton.imageset/ScanBackwardButton@3x.png differ diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/Contents.json b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/Contents.json new file mode 100644 index 00000000..8f07e000 --- /dev/null +++ b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ScanForwardButton.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ScanForwardButton@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ScanForwardButton@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton.png b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton.png new file mode 100644 index 00000000..43e251ec Binary files /dev/null and b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton.png differ diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@2x.png b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@2x.png new file mode 100644 index 00000000..bf28ce79 Binary files /dev/null and b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@2x.png differ diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@3x.png b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@3x.png new file mode 100644 index 00000000..f8ccaf93 Binary files /dev/null and b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Images.xcassets/ScanForwardButton.imageset/ScanForwardButton@3x.png differ diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Info.plist b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Info.plist new file mode 100644 index 00000000..a1853be2 --- /dev/null +++ b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + Main + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/PlayerView.swift b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/PlayerView.swift new file mode 100644 index 00000000..61dd42ca --- /dev/null +++ b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/PlayerView.swift @@ -0,0 +1,31 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + Player view backed by an AVPlayerLayer. +*/ + +import UIKit +import AVFoundation + +/// A simple `UIView` subclass that is backed by an `AVPlayerLayer` layer. +class PlayerView: UIView { + var player: AVPlayer? { + get { + return playerLayer.player + } + + set { + playerLayer.player = newValue + } + } + + var playerLayer: AVPlayerLayer { + return layer as! AVPlayerLayer + } + + override class var layerClass: AnyClass { + return AVPlayerLayer.self + } +} diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/PlayerViewController.swift b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/PlayerViewController.swift new file mode 100644 index 00000000..8e8870e8 --- /dev/null +++ b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/PlayerViewController.swift @@ -0,0 +1,359 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + View controller containing a player view and basic playback controls. +*/ + +import Foundation +import AVFoundation +import UIKit + +/* + KVO context used to differentiate KVO callbacks for this class versus other + classes in its class hierarchy. +*/ +private var playerViewControllerKVOContext = 0 + +class PlayerViewController: UIViewController { + // MARK: Properties + + // Attempt load and test these asset keys before playing. + static let assetKeysRequiredToPlay = [ + "playable", + "hasProtectedContent" + ] + + let player = AVPlayer() + + var currentTime: Double { + get { + return CMTimeGetSeconds(player.currentTime()) + } + set { + let newTime = CMTimeMakeWithSeconds(newValue, 1) + player.seek(to: newTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero) + } + } + + var duration: Double { + guard let currentItem = player.currentItem else { return 0.0 } + + return CMTimeGetSeconds(currentItem.duration) + } + + var rate: Float { + get { + return player.rate + } + + set { + player.rate = newValue + } + } + + var asset: AVURLAsset? { + didSet { + guard let newAsset = asset else { return } + + asynchronouslyLoadURLAsset(newAsset) + } + } + + private var playerLayer: AVPlayerLayer? { + return playerView.playerLayer + } + + /* + A formatter for individual date components used to provide an appropriate + value for the `startTimeLabel` and `durationLabel`. + */ + let timeRemainingFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.zeroFormattingBehavior = .pad + formatter.allowedUnits = [.minute, .second] + + return formatter + }() + + /* + A token obtained from calling `player`'s `addPeriodicTimeObserverForInterval(_:queue:usingBlock:)` + method. + */ + private var timeObserverToken: Any? + + private var playerItem: AVPlayerItem? = nil { + didSet { + /* + If needed, configure player item here before associating it with a player. + (example: adding outputs, setting text style rules, selecting media options) + */ + player.replaceCurrentItem(with: self.playerItem) + } + } + + // MARK: - IBOutlets + + @IBOutlet weak var timeSlider: UISlider! + @IBOutlet weak var startTimeLabel: UILabel! + @IBOutlet weak var durationLabel: UILabel! + @IBOutlet weak var rewindButton: UIButton! + @IBOutlet weak var playPauseButton: UIButton! + @IBOutlet weak var fastForwardButton: UIButton! + @IBOutlet weak var playerView: PlayerView! + + // MARK: - View Controller + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + /* + Update the UI when these player properties change. + + Use the context parameter to distinguish KVO for our particular observers + and not those destined for a subclass that also happens to be observing + these properties. + */ + addObserver(self, forKeyPath: #keyPath(PlayerViewController.player.currentItem.duration), options: [.new, .initial], context: &playerViewControllerKVOContext) + addObserver(self, forKeyPath: #keyPath(PlayerViewController.player.rate), options: [.new, .initial], context: &playerViewControllerKVOContext) + addObserver(self, forKeyPath: #keyPath(PlayerViewController.player.currentItem.status), options: [.new, .initial], context: &playerViewControllerKVOContext) + + playerView.playerLayer.player = player + + let movieURL = Bundle.main.url(forResource: "ElephantSeals", withExtension: "mov")! + asset = AVURLAsset(url: movieURL, options: nil) + + // Make sure we don't have a strong reference cycle by only capturing self as weak. + let interval = CMTimeMake(1, 1) + timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main) { [unowned self] time in + let timeElapsed = Float(CMTimeGetSeconds(time)) + + self.timeSlider.value = Float(timeElapsed) + self.startTimeLabel.text = self.createTimeString(time: timeElapsed) + } + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + if let timeObserverToken = timeObserverToken { + player.removeTimeObserver(timeObserverToken) + self.timeObserverToken = nil + } + + player.pause() + + removeObserver(self, forKeyPath: #keyPath(PlayerViewController.player.currentItem.duration), context: &playerViewControllerKVOContext) + removeObserver(self, forKeyPath: #keyPath(PlayerViewController.player.rate), context: &playerViewControllerKVOContext) + removeObserver(self, forKeyPath: #keyPath(PlayerViewController.player.currentItem.status), context: &playerViewControllerKVOContext) + } + + // MARK: - Asset Loading + + func asynchronouslyLoadURLAsset(_ newAsset: AVURLAsset) { + /* + Using AVAsset now runs the risk of blocking the current thread (the + main UI thread) whilst I/O happens to populate the properties. It's + prudent to defer our work until the properties we need have been loaded. + */ + newAsset.loadValuesAsynchronously(forKeys: PlayerViewController.assetKeysRequiredToPlay) { + /* + The asset invokes its completion handler on an arbitrary queue. + To avoid multiple threads using our internal state at the same time + we'll elect to use the main thread at all times, let's dispatch + our handler to the main queue. + */ + DispatchQueue.main.async { + /* + `self.asset` has already changed! No point continuing because + another `newAsset` will come along in a moment. + */ + guard newAsset == self.asset else { return } + + /* + Test whether the values of each of the keys we need have been + successfully loaded. + */ + for key in PlayerViewController.assetKeysRequiredToPlay { + var error: NSError? + + if newAsset.statusOfValue(forKey: key, error: &error) == .failed { + let stringFormat = NSLocalizedString("error.asset_key_%@_failed.description", comment: "Can't use this AVAsset because one of it's keys failed to load") + + let message = String.localizedStringWithFormat(stringFormat, key) + + self.handleErrorWithMessage(message, error: error) + + return + } + } + + // We can't play this asset. + if !newAsset.isPlayable || newAsset.hasProtectedContent { + let message = NSLocalizedString("error.asset_not_playable.description", comment: "Can't use this AVAsset because it isn't playable or has protected content") + + self.handleErrorWithMessage(message) + + return + } + + /* + We can play this asset. Create a new `AVPlayerItem` and make + it our player's current item. + */ + self.playerItem = AVPlayerItem(asset: newAsset) + } + } + } + + // MARK: - IBActions + + @IBAction func playPauseButtonWasPressed(_ sender: UIButton) { + if player.rate != 1.0 { + // Not playing forward, so play. + if currentTime == duration { + // At end, so got back to begining. + currentTime = 0.0 + } + + player.play() + } + else { + // Playing, so pause. + player.pause() + } + } + + @IBAction func rewindButtonWasPressed(_ sender: UIButton) { + // Rewind no faster than -2.0. + rate = max(player.rate - 2.0, -2.0) + } + + @IBAction func fastForwardButtonWasPressed(_ sender: UIButton) { + // Fast forward no faster than 2.0. + rate = min(player.rate + 2.0, 2.0) + } + + @IBAction func timeSliderDidChange(_ sender: UISlider) { + currentTime = Double(sender.value) + } + + // MARK: - KVO Observation + + // Update our UI when player or `player.currentItem` changes. + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { + // Make sure the this KVO callback was intended for this view controller. + guard context == &playerViewControllerKVOContext else { + super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) + return + } + + if keyPath == #keyPath(PlayerViewController.player.currentItem.duration) { + // Update timeSlider and enable/disable controls when duration > 0.0 + + /* + Handle `NSNull` value for `NSKeyValueChangeNewKey`, i.e. when + `player.currentItem` is nil. + */ + let newDuration: CMTime + if let newDurationAsValue = change?[NSKeyValueChangeKey.newKey] as? NSValue { + newDuration = newDurationAsValue.timeValue + } + else { + newDuration = kCMTimeZero + } + + let hasValidDuration = newDuration.isNumeric && newDuration.value != 0 + let newDurationSeconds = hasValidDuration ? CMTimeGetSeconds(newDuration) : 0.0 + let currentTime = hasValidDuration ? Float(CMTimeGetSeconds(player.currentTime())) : 0.0 + + timeSlider.maximumValue = Float(newDurationSeconds) + + timeSlider.value = currentTime + + rewindButton.isEnabled = hasValidDuration + + playPauseButton.isEnabled = hasValidDuration + + fastForwardButton.isEnabled = hasValidDuration + + timeSlider.isEnabled = hasValidDuration + + startTimeLabel.isEnabled = hasValidDuration + startTimeLabel.text = createTimeString(time: currentTime) + + durationLabel.isEnabled = hasValidDuration + durationLabel.text = createTimeString(time: Float(newDurationSeconds)) + } + else if keyPath == #keyPath(PlayerViewController.player.rate) { + // Update `playPauseButton` image. + + let newRate = (change?[NSKeyValueChangeKey.newKey] as! NSNumber).doubleValue + + let buttonImageName = newRate == 1.0 ? "PauseButton" : "PlayButton" + + let buttonImage = UIImage(named: buttonImageName) + + playPauseButton.setImage(buttonImage, for: UIControlState()) + } + else if keyPath == #keyPath(PlayerViewController.player.currentItem.status) { + // Display an error if status becomes `.Failed`. + + /* + Handle `NSNull` value for `NSKeyValueChangeNewKey`, i.e. when + `player.currentItem` is nil. + */ + let newStatus: AVPlayerItemStatus + + if let newStatusAsNumber = change?[NSKeyValueChangeKey.newKey] as? NSNumber { + newStatus = AVPlayerItemStatus(rawValue: newStatusAsNumber.intValue)! + } + else { + newStatus = .unknown + } + + if newStatus == .failed { + handleErrorWithMessage(player.currentItem?.error?.localizedDescription, error:player.currentItem?.error) + } + } + } + + // Trigger KVO for anyone observing our properties affected by player and player.currentItem + override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set { + let affectedKeyPathsMappingByKey: [String: Set] = [ + "duration": [#keyPath(PlayerViewController.player.currentItem.duration)], + "rate": [#keyPath(PlayerViewController.player.rate)] + ] + + return affectedKeyPathsMappingByKey[key] ?? super.keyPathsForValuesAffectingValue(forKey: key) + } + + // MARK: - Error Handling + + func handleErrorWithMessage(_ message: String?, error: Error? = nil) { + NSLog("Error occured with message: \(message), error: \(error).") + + let alertTitle = NSLocalizedString("alert.error.title", comment: "Alert title for errors") + let defaultAlertMessage = NSLocalizedString("error.default.description", comment: "Default error message when no NSError provided") + + let alert = UIAlertController(title: alertTitle, message: message == nil ? defaultAlertMessage : message, preferredStyle: UIAlertControllerStyle.alert) + + let alertActionTitle = NSLocalizedString("alert.error.actions.OK", comment: "OK on error alert") + + let alertAction = UIAlertAction(title: alertActionTitle, style: .default, handler: nil) + + alert.addAction(alertAction) + + present(alert, animated: true, completion: nil) + } + + // MARK: Convenience + + func createTimeString(time: Float) -> String { + let components = NSDateComponents() + components.second = Int(max(0.0, time)) + + return timeRemainingFormatter.string(from: components as DateComponents)! + } +} diff --git a/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/en.lproj/Localizable.strings b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/en.lproj/Localizable.strings new file mode 100644 index 00000000..6df6625d --- /dev/null +++ b/AVFoundationSimplePlayer/Swift/AVFoundationSimplePlayer-iOS/en.lproj/Localizable.strings @@ -0,0 +1,22 @@ +/* + + + Localizable strings. + + +*/ + +// Alert title for errors. +"alert.error.title" = "Error"; + +// OK action on error alert +"alert.error.actions.OK" = "OK on error alert"; + +// Can't use this AVAsset because one of it's keys failed to load. +"error.asset_key_%@_failed.description" = "Media failed to load key \"%@\""; + +// Can't use this AVAsset because it isn't playable or has protected content. +"error.asset_not_playable.description" = "Media isn't playable or has protected content"; + +// Default error message when no NSError provided. +"error.default.description" = "Unknown error";