AVFoundationSimplePlayer: Version 2.0, 2016-09-13
Updated to Swift 3 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 open a movie file and then how to implement various functionality including play, pause, fast forward, rewind, volume adjustment, time slider updating, and scrubbing.
|
@ -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.
|
|
@ -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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
D2385B4C1AF5181400DC8ADE /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
D2385B511AF5181400DC8ADE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D23E499D1B0D2DF90013F574 /* AAPLAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AAPLAppDelegate.h; sourceTree = "<group>"; };
|
||||
D23E499E1B0D30AF0013F574 /* AAPLAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AAPLAppDelegate.m; sourceTree = "<group>"; };
|
||||
D23E49A01B0D325D0013F574 /* AAPLPlayerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AAPLPlayerViewController.h; sourceTree = "<group>"; };
|
||||
D274899E1B1CD92F0020D82A /* AAPLPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AAPLPlayerView.h; sourceTree = "<group>"; };
|
||||
D274899F1B1CD92F0020D82A /* AAPLPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AAPLPlayerView.m; sourceTree = "<group>"; };
|
||||
D27489A21B1CD9370020D82A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
D2C148E41B0FA7AD004F41DA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
D2CF22161B1E5EAD0006C864 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
|
||||
/* 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 = "<group>";
|
||||
};
|
||||
D2385B391AF5181400DC8ADE = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D2CF22161B1E5EAD0006C864 /* README.md */,
|
||||
D2385B441AF5181400DC8ADE /* AVFoundationSimplePlayer-iOS */,
|
||||
D2385B431AF5181400DC8ADE /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D2385B431AF5181400DC8ADE /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D2385B421AF5181400DC8ADE /* AVFoundationSimplePlayer-ObjC.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
D2C148E31B0FA76B004F41DA /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D2C148E41B0FA7AD004F41DA /* main.m */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* 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 = "<group>";
|
||||
};
|
||||
D27489A11B1CD9370020D82A /* Localizable.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
D27489A21B1CD9370020D82A /* en */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* 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 */;
|
||||
}
|
|
@ -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 <UIApplicationDelegate>
|
||||
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
|
||||
@end
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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<NSObject> _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
|
|
@ -0,0 +1,136 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8121.17" systemVersion="14E8" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8101.14"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Player View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="AAPLPlayerViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xrl-4x-Edh">
|
||||
<rect key="frame" x="70" y="550" width="70" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="5cK-ah-alZ"/>
|
||||
<constraint firstAttribute="width" constant="70" id="E4B-3O-1NJ"/>
|
||||
</constraints>
|
||||
<state key="normal" image="PlayButton"/>
|
||||
<connections>
|
||||
<action selector="playPauseButtonWasPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="FYC-AI-liU"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="AcP-gW-NjJ">
|
||||
<rect key="frame" x="140" y="550" width="70" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="Ds3-V4-PI5"/>
|
||||
<constraint firstAttribute="width" constant="70" id="cXK-QP-qNN"/>
|
||||
</constraints>
|
||||
<state key="normal" image="ScanForwardButton"/>
|
||||
<connections>
|
||||
<action selector="fastForwardButtonWasPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="9VR-W7-z4u"/>
|
||||
</connections>
|
||||
</button>
|
||||
<slider opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="zs2-tO-O9K">
|
||||
<rect key="frame" x="218" y="550" width="364" height="31"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="Wah-wD-K8s"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<action selector="timeSliderDidChange:" destination="BYZ-38-t0r" eventType="valueChanged" id="iEm-kp-I1a"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="k2B-HR-6u1" customClass="AAPLPlayerView">
|
||||
<rect key="frame" x="0.0" y="20" width="600" height="500"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1Ph-2J-7JL">
|
||||
<rect key="frame" x="0.0" y="550" width="70" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="70" id="0HV-El-Nhy"/>
|
||||
<constraint firstAttribute="height" constant="30" id="Ju6-Bx-wC5"/>
|
||||
</constraints>
|
||||
<state key="normal" image="ScanBackwardButton"/>
|
||||
<connections>
|
||||
<action selector="rewindButtonWasPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="a5z-7z-Dbp"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0:00" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" enabled="NO" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lSE-mk-Glu" userLabel="StartTime">
|
||||
<rect key="frame" x="220" y="530" width="50" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="20" id="3FX-0e-O6P"/>
|
||||
<constraint firstAttribute="width" constant="50" id="FxO-it-ceh"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-:--" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" enabled="NO" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qOC-Kz-nZX" userLabel="Duration">
|
||||
<rect key="frame" x="530" y="530" width="50" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="50" id="dHY-sI-1Zo"/>
|
||||
<constraint firstAttribute="height" constant="20" id="vny-2p-5hq"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="leading" secondItem="lSE-mk-Glu" secondAttribute="leading" id="0Nl-Eq-0C9"/>
|
||||
<constraint firstItem="AcP-gW-NjJ" firstAttribute="leading" secondItem="xrl-4x-Edh" secondAttribute="trailing" id="0Xi-o4-GPm"/>
|
||||
<constraint firstItem="k2B-HR-6u1" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="8RV-Wu-cZ2"/>
|
||||
<constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="1Ph-2J-7JL" secondAttribute="bottom" constant="20" id="AN8-Ow-vgP"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="leading" secondItem="AcP-gW-NjJ" secondAttribute="trailing" constant="10" id="Djj-sA-9ve"/>
|
||||
<constraint firstItem="qOC-Kz-nZX" firstAttribute="trailing" secondItem="zs2-tO-O9K" secondAttribute="trailing" id="Ny2-em-Hsb"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="k2B-HR-6u1" secondAttribute="trailing" constant="-20" id="SHT-LM-7fv"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="width" secondItem="lSE-mk-Glu" secondAttribute="width" id="UTA-oc-lgo"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="centerY" secondItem="AcP-gW-NjJ" secondAttribute="centerY" id="aUq-JI-udW"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="centerX" secondItem="lSE-mk-Glu" secondAttribute="centerX" id="eau-ex-HCT"/>
|
||||
<constraint firstItem="k2B-HR-6u1" firstAttribute="top" secondItem="8bC-Xf-vdC" secondAttribute="topMargin" constant="70" id="fr7-hU-dZX"/>
|
||||
<constraint firstItem="xrl-4x-Edh" firstAttribute="centerY" secondItem="1Ph-2J-7JL" secondAttribute="centerY" id="gSS-SM-9qW"/>
|
||||
<constraint firstItem="k2B-HR-6u1" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" constant="-20" id="hIA-sm-1WB"/>
|
||||
<constraint firstItem="AcP-gW-NjJ" firstAttribute="centerY" secondItem="xrl-4x-Edh" secondAttribute="centerY" id="hnG-E0-mGw"/>
|
||||
<constraint firstItem="1Ph-2J-7JL" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" constant="-20" id="iAK-lW-ewa"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailingMargin" id="ill-9D-3bq"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="top" secondItem="lSE-mk-Glu" secondAttribute="bottom" id="jXv-uJ-vtR"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="top" secondItem="qOC-Kz-nZX" secondAttribute="bottom" id="mP2-9X-QOD"/>
|
||||
<constraint firstItem="xrl-4x-Edh" firstAttribute="leading" secondItem="1Ph-2J-7JL" secondAttribute="trailing" id="nWe-M1-J3B"/>
|
||||
<constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="k2B-HR-6u1" secondAttribute="bottom" constant="80" id="zi6-Fl-Tea"/>
|
||||
</constraints>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="fr7-hU-dZX"/>
|
||||
<exclude reference="UTA-oc-lgo"/>
|
||||
<exclude reference="eau-ex-HCT"/>
|
||||
</mask>
|
||||
</variation>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="durationLabel" destination="qOC-Kz-nZX" id="vdA-zn-17E"/>
|
||||
<outlet property="fastForwardButton" destination="AcP-gW-NjJ" id="bVk-tK-Ap4"/>
|
||||
<outlet property="playPauseButton" destination="xrl-4x-Edh" id="BeD-Na-Ocg"/>
|
||||
<outlet property="playerView" destination="k2B-HR-6u1" id="w2i-er-gei"/>
|
||||
<outlet property="rewindButton" destination="1Ph-2J-7JL" id="Fwp-1T-h4Q"/>
|
||||
<outlet property="startTimeLabel" destination="lSE-mk-Glu" id="0k2-Ci-tDY"/>
|
||||
<outlet property="timeSlider" destination="zs2-tO-O9K" id="BKu-N7-khJ"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="357" y="421"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="PlayButton" width="21" height="22"/>
|
||||
<image name="ScanBackwardButton" width="29" height="16"/>
|
||||
<image name="ScanForwardButton" width="29" height="16"/>
|
||||
</resources>
|
||||
</document>
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 987 B |
After Width: | Height: | Size: 188 B |
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 637 B |
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>Main</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<true/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
<samplecode>
|
||||
<abstract>
|
||||
Localizable strings.
|
||||
</abstract>
|
||||
</samplecode>
|
||||
*/
|
||||
|
||||
// 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";
|
|
@ -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 <UIKit/UIKit.h>
|
||||
#import "AAPLAppDelegate.h"
|
||||
|
||||
|
||||
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AAPLAppDelegate class]));
|
||||
}
|
||||
}
|
|
@ -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.
|
|
@ -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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
D2385B4A1AF5181400DC8ADE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
D2385B4C1AF5181400DC8ADE /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
D2385B511AF5181400DC8ADE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D27489961B1CD4E00020D82A /* PlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = "<group>"; };
|
||||
D27489991B1CD4EB0020D82A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
D2CF22151B1E5E970006C864 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
|
||||
/* 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 = "<group>";
|
||||
};
|
||||
D2385B391AF5181400DC8ADE = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D2CF22151B1E5E970006C864 /* README.md */,
|
||||
D2385B441AF5181400DC8ADE /* AVFoundationSimplePlayer-iOS */,
|
||||
D2385B431AF5181400DC8ADE /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D2385B431AF5181400DC8ADE /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D2385B421AF5181400DC8ADE /* AVFoundationSimplePlayer-Swift.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
/* 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 = "<group>";
|
||||
};
|
||||
D27489981B1CD4EB0020D82A /* Localizable.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
D27489991B1CD4EB0020D82A /* en */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* 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 */;
|
||||
}
|
|
@ -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?
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8121.17" systemVersion="14E8" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8101.14"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Player View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="PlayerViewController" customModule="AVFoundationSimplePlayer_Swift" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xrl-4x-Edh">
|
||||
<rect key="frame" x="70" y="550" width="70" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="5cK-ah-alZ"/>
|
||||
<constraint firstAttribute="width" constant="70" id="E4B-3O-1NJ"/>
|
||||
</constraints>
|
||||
<state key="normal" image="PlayButton"/>
|
||||
<connections>
|
||||
<action selector="playPauseButtonWasPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="FYC-AI-liU"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="AcP-gW-NjJ">
|
||||
<rect key="frame" x="140" y="550" width="70" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="Ds3-V4-PI5"/>
|
||||
<constraint firstAttribute="width" constant="70" id="cXK-QP-qNN"/>
|
||||
</constraints>
|
||||
<state key="normal" image="ScanForwardButton"/>
|
||||
<connections>
|
||||
<action selector="fastForwardButtonWasPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="9VR-W7-z4u"/>
|
||||
</connections>
|
||||
</button>
|
||||
<slider opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="zs2-tO-O9K">
|
||||
<rect key="frame" x="218" y="550" width="364" height="31"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="Wah-wD-K8s"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<action selector="timeSliderDidChange:" destination="BYZ-38-t0r" eventType="valueChanged" id="iEm-kp-I1a"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="k2B-HR-6u1" customClass="PlayerView" customModule="AVFoundationSimplePlayer_Swift" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="20" width="600" height="500"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1Ph-2J-7JL">
|
||||
<rect key="frame" x="0.0" y="550" width="70" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="70" id="0HV-El-Nhy"/>
|
||||
<constraint firstAttribute="height" constant="30" id="Ju6-Bx-wC5"/>
|
||||
</constraints>
|
||||
<state key="normal" image="ScanBackwardButton"/>
|
||||
<connections>
|
||||
<action selector="rewindButtonWasPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="a5z-7z-Dbp"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0:00" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" enabled="NO" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lSE-mk-Glu" userLabel="StartTime">
|
||||
<rect key="frame" x="220" y="530" width="50" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="20" id="3FX-0e-O6P"/>
|
||||
<constraint firstAttribute="width" constant="50" id="FxO-it-ceh"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-:--" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" enabled="NO" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qOC-Kz-nZX" userLabel="Duration">
|
||||
<rect key="frame" x="530" y="530" width="50" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="50" id="dHY-sI-1Zo"/>
|
||||
<constraint firstAttribute="height" constant="20" id="vny-2p-5hq"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="leading" secondItem="lSE-mk-Glu" secondAttribute="leading" id="0Nl-Eq-0C9"/>
|
||||
<constraint firstItem="AcP-gW-NjJ" firstAttribute="leading" secondItem="xrl-4x-Edh" secondAttribute="trailing" id="0Xi-o4-GPm"/>
|
||||
<constraint firstItem="k2B-HR-6u1" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="8RV-Wu-cZ2"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="leading" secondItem="AcP-gW-NjJ" secondAttribute="trailing" constant="10" id="Djj-sA-9ve"/>
|
||||
<constraint firstItem="qOC-Kz-nZX" firstAttribute="trailing" secondItem="zs2-tO-O9K" secondAttribute="trailing" id="Ny2-em-Hsb"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="k2B-HR-6u1" secondAttribute="trailing" constant="-20" id="SHT-LM-7fv"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="width" secondItem="lSE-mk-Glu" secondAttribute="width" id="UTA-oc-lgo"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="centerY" secondItem="AcP-gW-NjJ" secondAttribute="centerY" id="aUq-JI-udW"/>
|
||||
<constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="1Ph-2J-7JL" secondAttribute="bottom" constant="20" id="dYL-aD-qru"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="centerX" secondItem="lSE-mk-Glu" secondAttribute="centerX" id="eau-ex-HCT"/>
|
||||
<constraint firstItem="k2B-HR-6u1" firstAttribute="top" secondItem="8bC-Xf-vdC" secondAttribute="topMargin" constant="70" id="fr7-hU-dZX"/>
|
||||
<constraint firstItem="xrl-4x-Edh" firstAttribute="centerY" secondItem="1Ph-2J-7JL" secondAttribute="centerY" id="gSS-SM-9qW"/>
|
||||
<constraint firstItem="k2B-HR-6u1" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" constant="-20" id="hIA-sm-1WB"/>
|
||||
<constraint firstItem="AcP-gW-NjJ" firstAttribute="centerY" secondItem="xrl-4x-Edh" secondAttribute="centerY" id="hnG-E0-mGw"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailingMargin" id="ill-9D-3bq"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="top" secondItem="lSE-mk-Glu" secondAttribute="bottom" id="jXv-uJ-vtR"/>
|
||||
<constraint firstItem="1Ph-2J-7JL" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" constant="-20" id="kCK-L8-VXv"/>
|
||||
<constraint firstItem="zs2-tO-O9K" firstAttribute="top" secondItem="qOC-Kz-nZX" secondAttribute="bottom" id="mP2-9X-QOD"/>
|
||||
<constraint firstItem="xrl-4x-Edh" firstAttribute="leading" secondItem="1Ph-2J-7JL" secondAttribute="trailing" id="nWe-M1-J3B"/>
|
||||
<constraint firstItem="1Ph-2J-7JL" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" constant="20" id="pTa-hD-fe7"/>
|
||||
<constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="k2B-HR-6u1" secondAttribute="bottom" constant="80" id="zi6-Fl-Tea"/>
|
||||
</constraints>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="fr7-hU-dZX"/>
|
||||
<exclude reference="pTa-hD-fe7"/>
|
||||
<exclude reference="UTA-oc-lgo"/>
|
||||
<exclude reference="eau-ex-HCT"/>
|
||||
</mask>
|
||||
</variation>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="durationLabel" destination="qOC-Kz-nZX" id="vdA-zn-17E"/>
|
||||
<outlet property="fastForwardButton" destination="AcP-gW-NjJ" id="bVk-tK-Ap4"/>
|
||||
<outlet property="playPauseButton" destination="xrl-4x-Edh" id="BeD-Na-Ocg"/>
|
||||
<outlet property="playerView" destination="k2B-HR-6u1" id="w2i-er-gei"/>
|
||||
<outlet property="rewindButton" destination="1Ph-2J-7JL" id="Fwp-1T-h4Q"/>
|
||||
<outlet property="startTimeLabel" destination="lSE-mk-Glu" id="0k2-Ci-tDY"/>
|
||||
<outlet property="timeSlider" destination="zs2-tO-O9K" id="BKu-N7-khJ"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="357" y="421"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="PlayButton" width="21" height="22"/>
|
||||
<image name="ScanBackwardButton" width="29" height="16"/>
|
||||
<image name="ScanForwardButton" width="29" height="16"/>
|
||||
</resources>
|
||||
</document>
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 987 B |
After Width: | Height: | Size: 188 B |
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 637 B |
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>Main</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<true/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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<String> {
|
||||
let affectedKeyPathsMappingByKey: [String: Set<String>] = [
|
||||
"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)!
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
<samplecode>
|
||||
<abstract>
|
||||
Localizable strings.
|
||||
</abstract>
|
||||
</samplecode>
|
||||
*/
|
||||
|
||||
// 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";
|