From 03c07fb4cc51fedf21b85eda7a43cefebca6b50b Mon Sep 17 00:00:00 2001 From: liviur Date: Wed, 14 Sep 2016 14:22:40 -0400 Subject: [PATCH] modernize and fix example project --- Project/youtube-player-ios-example/Podfile | 17 +- .../project.pbxproj | 90 ++- .../AppIcon.appiconset/Contents.json | 10 + .../PlaylistViewController.h | 2 +- .../SingleVideoViewController.h | 2 +- .../youtube-player-ios-example-Info.plist | 2 +- ...youtube-player-ios-exampleTests-Info.plist | 2 +- .../Assets/YTPlayerView-iframe-player.html | 90 +++ .../Headers/YTPlayerView.h | 693 ++++++++++++++++++ .../youtube-ios-player-helper-umbrella.h | 7 + .../Info.plist | Bin 0 -> 834 bytes .../Modules/module.modulemap | 6 + .../youtube_ios_player_helper | Bin 0 -> 92400 bytes 13 files changed, 891 insertions(+), 30 deletions(-) create mode 100644 Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Assets.bundle/Assets/YTPlayerView-iframe-player.html create mode 100644 Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Headers/YTPlayerView.h create mode 100644 Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Headers/youtube-ios-player-helper-umbrella.h create mode 100644 Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Info.plist create mode 100644 Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Modules/module.modulemap create mode 100755 Project/youtube-player-ios-example/youtube_ios_player_helper.framework/youtube_ios_player_helper diff --git a/Project/youtube-player-ios-example/Podfile b/Project/youtube-player-ios-example/Podfile index de92228..81697ed 100644 --- a/Project/youtube-player-ios-example/Podfile +++ b/Project/youtube-player-ios-example/Podfile @@ -12,8 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -pod "youtube-ios-player-helper", :path => "../../youtube-ios-player-helper.podspec" +platform :ios, '8.0' +use_frameworks! +install! 'cocoapods', :deterministic_uuids => false -target "youtube-player-ios-exampleTests", :exclusive => true do - pod "OCMock", "3.1.2" +def shared_pods + pod 'youtube-ios-player-helper', :path => '../../youtube-ios-player-helper.podspec' +end + +target 'youtube-player-ios-example' do + shared_pods +end + +target 'youtube-player-ios-exampleTests' do + shared_pods + pod 'OCMock', '3.1.2' end diff --git a/Project/youtube-player-ios-example/youtube-player-ios-example.xcodeproj/project.pbxproj b/Project/youtube-player-ios-example/youtube-player-ios-example.xcodeproj/project.pbxproj index 28a361e..7e4faf5 100644 --- a/Project/youtube-player-ios-example/youtube-player-ios-example.xcodeproj/project.pbxproj +++ b/Project/youtube-player-ios-example/youtube-player-ios-example.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0807612906EA99DE1F308109 /* Pods_youtube_player_ios_example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 20854B26608C699678F379DD /* Pods_youtube_player_ios_example.framework */; }; 4D6993E218E22E0C0073680F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D6993E118E22E0C0073680F /* Foundation.framework */; }; 4D6993E418E22E0C0073680F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D6993E318E22E0C0073680F /* CoreGraphics.framework */; }; 4D6993E618E22E0C0073680F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D6993E518E22E0C0073680F /* UIKit.framework */; }; @@ -22,8 +23,7 @@ 4D69941F18E22EE10073680F /* SingleVideoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D69941A18E22EE10073680F /* SingleVideoViewController.m */; }; 4D69942018E22EE10073680F /* PlaylistViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D69941C18E22EE10073680F /* PlaylistViewController.m */; }; 4D69942218E22F000073680F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D69942118E22F000073680F /* main.m */; }; - A1667DB27CF64FC392012064 /* libPods-youtube-player-ios-exampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 97FE8C7A7A914485BBD9A3D4 /* libPods-youtube-player-ios-exampleTests.a */; }; - A732EFB889AD44979951554A /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 614CB957CD094425884C83E1 /* libPods.a */; }; + F15E71EC3B679A32175080B3 /* Pods_youtube_player_ios_exampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 893FFC22B6F75FD7806C973D /* Pods_youtube_player_ios_exampleTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -37,7 +37,9 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 20854B26608C699678F379DD /* Pods_youtube_player_ios_example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_youtube_player_ios_example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 41DE6A7D49F386BF08C18468 /* Pods-youtube-player-ios-exampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-youtube-player-ios-exampleTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-youtube-player-ios-exampleTests/Pods-youtube-player-ios-exampleTests.release.xcconfig"; sourceTree = ""; }; + 443B714F5DDE2A774C61BAA4 /* Pods-youtube-player-ios-example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-youtube-player-ios-example.release.xcconfig"; path = "Pods/Target Support Files/Pods-youtube-player-ios-example/Pods-youtube-player-ios-example.release.xcconfig"; sourceTree = ""; }; 4D6993DE18E22E0C0073680F /* youtube-player-ios-example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "youtube-player-ios-example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4D6993E118E22E0C0073680F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 4D6993E318E22E0C0073680F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -59,9 +61,9 @@ 4D69941B18E22EE10073680F /* PlaylistViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaylistViewController.h; sourceTree = ""; }; 4D69941C18E22EE10073680F /* PlaylistViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlaylistViewController.m; sourceTree = ""; }; 4D69942118E22F000073680F /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 5EDBAF28CE586F953081FFD5 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; 614CB957CD094425884C83E1 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 97FE8C7A7A914485BBD9A3D4 /* libPods-youtube-player-ios-exampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-youtube-player-ios-exampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7D44490A683E920C4C60E44E /* Pods-youtube-player-ios-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-youtube-player-ios-example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-youtube-player-ios-example/Pods-youtube-player-ios-example.debug.xcconfig"; sourceTree = ""; }; + 893FFC22B6F75FD7806C973D /* Pods_youtube_player_ios_exampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_youtube_player_ios_exampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9F1A73947F21B4AC48269A4E /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; A33CC11F5E5B159D09ADCB5D /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; C9EAC06935E4E004160720A4 /* Pods-youtube-player-ios-exampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-youtube-player-ios-exampleTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-youtube-player-ios-exampleTests/Pods-youtube-player-ios-exampleTests.debug.xcconfig"; sourceTree = ""; }; @@ -75,7 +77,7 @@ 4D6993E418E22E0C0073680F /* CoreGraphics.framework in Frameworks */, 4D6993E618E22E0C0073680F /* UIKit.framework in Frameworks */, 4D6993E218E22E0C0073680F /* Foundation.framework in Frameworks */, - A732EFB889AD44979951554A /* libPods.a in Frameworks */, + 0807612906EA99DE1F308109 /* Pods_youtube_player_ios_example.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -86,7 +88,7 @@ 4D69940118E22E0C0073680F /* XCTest.framework in Frameworks */, 4D69940318E22E0C0073680F /* UIKit.framework in Frameworks */, 4D69940218E22E0C0073680F /* Foundation.framework in Frameworks */, - A1667DB27CF64FC392012064 /* libPods-youtube-player-ios-exampleTests.a in Frameworks */, + F15E71EC3B679A32175080B3 /* Pods_youtube_player_ios_exampleTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -121,7 +123,8 @@ 4D6993E518E22E0C0073680F /* UIKit.framework */, 4D69940018E22E0C0073680F /* XCTest.framework */, 614CB957CD094425884C83E1 /* libPods.a */, - 97FE8C7A7A914485BBD9A3D4 /* libPods-youtube-player-ios-exampleTests.a */, + 20854B26608C699678F379DD /* Pods_youtube_player_ios_example.framework */, + 893FFC22B6F75FD7806C973D /* Pods_youtube_player_ios_exampleTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -178,6 +181,8 @@ A33CC11F5E5B159D09ADCB5D /* Pods.release.xcconfig */, C9EAC06935E4E004160720A4 /* Pods-youtube-player-ios-exampleTests.debug.xcconfig */, 41DE6A7D49F386BF08C18468 /* Pods-youtube-player-ios-exampleTests.release.xcconfig */, + 7D44490A683E920C4C60E44E /* Pods-youtube-player-ios-example.debug.xcconfig */, + 443B714F5DDE2A774C61BAA4 /* Pods-youtube-player-ios-example.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -189,11 +194,12 @@ isa = PBXNativeTarget; buildConfigurationList = 4D69941018E22E0C0073680F /* Build configuration list for PBXNativeTarget "youtube-player-ios-example" */; buildPhases = ( - FE80856E09B8459AAE18655D /* Check Pods Manifest.lock */, + FE80856E09B8459AAE18655D /* [CP] Check Pods Manifest.lock */, 4D6993DA18E22E0C0073680F /* Sources */, 4D6993DB18E22E0C0073680F /* Frameworks */, 4D6993DC18E22E0C0073680F /* Resources */, - 8D2FF9AA090848A181BEBD44 /* Copy Pods Resources */, + 8D2FF9AA090848A181BEBD44 /* [CP] Copy Pods Resources */, + 821C76DBBDF6370F27E6B04E /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -208,11 +214,12 @@ isa = PBXNativeTarget; buildConfigurationList = 4D69941318E22E0C0073680F /* Build configuration list for PBXNativeTarget "youtube-player-ios-exampleTests" */; buildPhases = ( - 40A67310F9C54613B3075C8C /* Check Pods Manifest.lock */, + 40A67310F9C54613B3075C8C /* [CP] Check Pods Manifest.lock */, 4D6993FB18E22E0C0073680F /* Sources */, 4D6993FC18E22E0C0073680F /* Frameworks */, 4D6993FD18E22E0C0073680F /* Resources */, - B16AF54440614B7BBEA8906E /* Copy Pods Resources */, + B16AF54440614B7BBEA8906E /* [CP] Copy Pods Resources */, + 9443987232F3AFC68C9E26D8 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -230,7 +237,7 @@ 4D6993D618E22E0C0073680F /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0510; + LastUpgradeCheck = 0730; ORGANIZATIONNAME = "YouTube Developer Relations"; TargetAttributes = { 4D6993FE18E22E0C0073680F = { @@ -279,14 +286,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 40A67310F9C54613B3075C8C /* Check Pods Manifest.lock */ = { + 40A67310F9C54613B3075C8C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -294,29 +301,59 @@ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - 8D2FF9AA090848A181BEBD44 /* Copy Pods Resources */ = { + 821C76DBBDF6370F27E6B04E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-youtube-player-ios-example/Pods-youtube-player-ios-example-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - B16AF54440614B7BBEA8906E /* Copy Pods Resources */ = { + 8D2FF9AA090848A181BEBD44 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-youtube-player-ios-example/Pods-youtube-player-ios-example-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9443987232F3AFC68C9E26D8 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-youtube-player-ios-exampleTests/Pods-youtube-player-ios-exampleTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B16AF54440614B7BBEA8906E /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -324,14 +361,14 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-youtube-player-ios-exampleTests/Pods-youtube-player-ios-exampleTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - FE80856E09B8459AAE18655D /* Check Pods Manifest.lock */ = { + FE80856E09B8459AAE18655D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -409,6 +446,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -463,13 +501,15 @@ }; 4D69941118E22E0C0073680F /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9F1A73947F21B4AC48269A4E /* Pods.debug.xcconfig */; + baseConfigurationReference = 7D44490A683E920C4C60E44E /* Pods-youtube-player-ios-example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "youtube-player-ios-example/youtube-player-ios-example-Prefix.pch"; INFOPLIST_FILE = "youtube-player-ios-example/youtube-player-ios-example-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.youtube.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; @@ -478,13 +518,15 @@ }; 4D69941218E22E0C0073680F /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A33CC11F5E5B159D09ADCB5D /* Pods.release.xcconfig */; + baseConfigurationReference = 443B714F5DDE2A774C61BAA4 /* Pods-youtube-player-ios-example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "youtube-player-ios-example/youtube-player-ios-example-Prefix.pch"; INFOPLIST_FILE = "youtube-player-ios-example/youtube-player-ios-example-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.youtube.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; @@ -508,6 +550,7 @@ "$(inherited)", ); INFOPLIST_FILE = "youtube-player-ios-exampleTests/youtube-player-ios-exampleTests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "com.youtube.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; @@ -527,6 +570,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "youtube-player-ios-example/youtube-player-ios-example-Prefix.pch"; INFOPLIST_FILE = "youtube-player-ios-exampleTests/youtube-player-ios-exampleTests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "com.youtube.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; diff --git a/Project/youtube-player-ios-example/youtube-player-ios-example/Images.xcassets/AppIcon.appiconset/Contents.json b/Project/youtube-player-ios-example/youtube-player-ios-example/Images.xcassets/AppIcon.appiconset/Contents.json index 33ec0bc..118c98f 100644 --- a/Project/youtube-player-ios-example/youtube-player-ios-example/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Project/youtube-player-ios-example/youtube-player-ios-example/Images.xcassets/AppIcon.appiconset/Contents.json @@ -5,11 +5,21 @@ "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", diff --git a/Project/youtube-player-ios-example/youtube-player-ios-example/PlaylistViewController.h b/Project/youtube-player-ios-example/youtube-player-ios-example/PlaylistViewController.h index a99a2be..81fd49d 100644 --- a/Project/youtube-player-ios-example/youtube-player-ios-example/PlaylistViewController.h +++ b/Project/youtube-player-ios-example/youtube-player-ios-example/PlaylistViewController.h @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "YTPlayerView.h" +@import youtube_ios_player_helper; @interface PlaylistViewController : UIViewController diff --git a/Project/youtube-player-ios-example/youtube-player-ios-example/SingleVideoViewController.h b/Project/youtube-player-ios-example/youtube-player-ios-example/SingleVideoViewController.h index a97023b..a0fe450 100644 --- a/Project/youtube-player-ios-example/youtube-player-ios-example/SingleVideoViewController.h +++ b/Project/youtube-player-ios-example/youtube-player-ios-example/SingleVideoViewController.h @@ -14,7 +14,7 @@ #import -#import "YTPlayerView.h" +@import youtube_ios_player_helper; @interface SingleVideoViewController : UIViewController diff --git a/Project/youtube-player-ios-example/youtube-player-ios-example/youtube-player-ios-example-Info.plist b/Project/youtube-player-ios-example/youtube-player-ios-example/youtube-player-ios-example-Info.plist index a9fc5fd..6667e41 100644 --- a/Project/youtube-player-ios-example/youtube-player-ios-example/youtube-player-ios-example-Info.plist +++ b/Project/youtube-player-ios-example/youtube-player-ios-example/youtube-player-ios-example-Info.plist @@ -9,7 +9,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - com.youtube.demo.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/Project/youtube-player-ios-example/youtube-player-ios-exampleTests/youtube-player-ios-exampleTests-Info.plist b/Project/youtube-player-ios-example/youtube-player-ios-exampleTests/youtube-player-ios-exampleTests-Info.plist index 676215e..169b6f7 100644 --- a/Project/youtube-player-ios-example/youtube-player-ios-exampleTests/youtube-player-ios-exampleTests-Info.plist +++ b/Project/youtube-player-ios-example/youtube-player-ios-exampleTests/youtube-player-ios-exampleTests-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - com.youtube.demo.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType diff --git a/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Assets.bundle/Assets/YTPlayerView-iframe-player.html b/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Assets.bundle/Assets/YTPlayerView-iframe-player.html new file mode 100644 index 0000000..975510e --- /dev/null +++ b/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Assets.bundle/Assets/YTPlayerView-iframe-player.html @@ -0,0 +1,90 @@ + + + + + + + +
+
+
+ + + + diff --git a/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Headers/YTPlayerView.h b/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Headers/YTPlayerView.h new file mode 100644 index 0000000..7923c15 --- /dev/null +++ b/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Headers/YTPlayerView.h @@ -0,0 +1,693 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@class YTPlayerView; + +/** These enums represent the state of the current video in the player. */ +typedef NS_ENUM(NSInteger, YTPlayerState) { + kYTPlayerStateUnstarted, + kYTPlayerStateEnded, + kYTPlayerStatePlaying, + kYTPlayerStatePaused, + kYTPlayerStateBuffering, + kYTPlayerStateQueued, + kYTPlayerStateUnknown +}; + +/** These enums represent the resolution of the currently loaded video. */ +typedef NS_ENUM(NSInteger, YTPlaybackQuality) { + kYTPlaybackQualitySmall, + kYTPlaybackQualityMedium, + kYTPlaybackQualityLarge, + kYTPlaybackQualityHD720, + kYTPlaybackQualityHD1080, + kYTPlaybackQualityHighRes, + kYTPlaybackQualityAuto, /** Addition for YouTube Live Events. */ + kYTPlaybackQualityDefault, + kYTPlaybackQualityUnknown /** This should never be returned. It is here for future proofing. */ +}; + +/** These enums represent error codes thrown by the player. */ +typedef NS_ENUM(NSInteger, YTPlayerError) { + kYTPlayerErrorInvalidParam, + kYTPlayerErrorHTML5Error, + kYTPlayerErrorVideoNotFound, // Functionally equivalent error codes 100 and + // 105 have been collapsed into |kYTPlayerErrorVideoNotFound|. + kYTPlayerErrorNotEmbeddable, // Functionally equivalent error codes 101 and + // 150 have been collapsed into |kYTPlayerErrorNotEmbeddable|. + kYTPlayerErrorUnknown +}; + +/** + * A delegate for ViewControllers to respond to YouTube player events outside + * of the view, such as changes to video playback state or playback errors. + * The callback functions correlate to the events fired by the IFrame API. + * For the full documentation, see the IFrame documentation here: + * https://developers.google.com/youtube/iframe_api_reference#Events + */ +@protocol YTPlayerViewDelegate + +@optional +/** + * Invoked when the player view is ready to receive API calls. + * + * @param playerView The YTPlayerView instance that has become ready. + */ +- (void)playerViewDidBecomeReady:(nonnull YTPlayerView *)playerView; + +/** + * Callback invoked when player state has changed, e.g. stopped or started playback. + * + * @param playerView The YTPlayerView instance where playback state has changed. + * @param state YTPlayerState designating the new playback state. + */ +- (void)playerView:(nonnull YTPlayerView *)playerView didChangeToState:(YTPlayerState)state; + +/** + * Callback invoked when playback quality has changed. + * + * @param playerView The YTPlayerView instance where playback quality has changed. + * @param quality YTPlaybackQuality designating the new playback quality. + */ +- (void)playerView:(nonnull YTPlayerView *)playerView didChangeToQuality:(YTPlaybackQuality)quality; + +/** + * Callback invoked when an error has occured. + * + * @param playerView The YTPlayerView instance where the error has occurred. + * @param error YTPlayerError containing the error state. + */ +- (void)playerView:(nonnull YTPlayerView *)playerView receivedError:(YTPlayerError)error; + +/** + * Callback invoked frequently when playBack is plaing. + * + * @param playerView The YTPlayerView instance where the error has occurred. + * @param playTime float containing curretn playback time. + */ +- (void)playerView:(nonnull YTPlayerView *)playerView didPlayTime:(float)playTime; + +/** + * Callback invoked when setting up the webview to allow custom colours so it fits in + * with app color schemes. If a transparent view is required specify clearColor and + * the code will handle the opacity etc. + * + * @param playerView The YTPlayerView instance where the error has occurred. + * @return A color object that represents the background color of the webview. + */ +- (nonnull UIColor *)playerViewPreferredWebViewBackgroundColor:(nonnull YTPlayerView *)playerView; + +/** + * Callback invoked when initially loading the YouTube iframe to the webview to display a custom + * loading view while the player view is not ready. This loading view will be dismissed just before + * -playerViewDidBecomeReady: callback is invoked. The loading view will be automatically resized + * to cover the entire player view. + * + * The default implementation does not display any custom loading views so the player will display + * a blank view with a background color of (-playerViewPreferredWebViewBackgroundColor:). + * + * Note that the custom loading view WILL NOT be displayed after iframe is loaded. It will be + * handled by YouTube iframe API. This callback is just intended to tell users the view is actually + * doing something while iframe is being loaded, which will take some time if users are in poor networks. + * + * @param playerView The YTPlayerView instance where the error has occurred. + * @return A view object that will be displayed while YouTube iframe API is being loaded. + * Pass nil to display no custom loading view. Default implementation returns nil. + */ +- (nullable UIView *)playerViewPreferredInitialLoadingView:(nonnull YTPlayerView *)playerView; + +@end + +/** + * YTPlayerView is a custom UIView that client developers will use to include YouTube + * videos in their iOS applications. It can be instantiated programmatically, or via + * Interface Builder. Use the methods YTPlayerView::loadWithVideoId:, + * YTPlayerView::loadWithPlaylistId: or their variants to set the video or playlist + * to populate the view with. + */ +@interface YTPlayerView : UIView + +@property(nonatomic, strong, nullable, readonly) UIWebView *webView; + +/** A delegate to be notified on playback events. */ +@property(nonatomic, weak, nullable) id delegate; + +/** + * This method loads the player with the given video ID. + * This is a convenience method for calling YTPlayerView::loadPlayerWithVideoId:withPlayerVars: + * without player variables. + * + * This method reloads the entire contents of the UIWebView and regenerates its HTML contents. + * To change the currently loaded video without reloading the entire UIWebView, use the + * YTPlayerView::cueVideoById:startSeconds:suggestedQuality: family of methods. + * + * @param videoId The YouTube video ID of the video to load in the player view. + * @return YES if player has been configured correctly, NO otherwise. + */ +- (BOOL)loadWithVideoId:(nonnull NSString *)videoId; + +/** + * This method loads the player with the given playlist ID. + * This is a convenience method for calling YTPlayerView::loadWithPlaylistId:withPlayerVars: + * without player variables. + * + * This method reloads the entire contents of the UIWebView and regenerates its HTML contents. + * To change the currently loaded video without reloading the entire UIWebView, use the + * YTPlayerView::cuePlaylistByPlaylistId:index:startSeconds:suggestedQuality: + * family of methods. + * + * @param playlistId The YouTube playlist ID of the playlist to load in the player view. + * @return YES if player has been configured correctly, NO otherwise. + */ +- (BOOL)loadWithPlaylistId:(nonnull NSString *)playlistId; + +/** + * This method loads the player with the given video ID and player variables. Player variables + * specify optional parameters for video playback. For instance, to play a YouTube + * video inline, the following playerVars dictionary would be used: + * + * @code + * @{ @"playsinline" : @1 }; + * @endcode + * + * Note that when the documentation specifies a valid value as a number (typically 0, 1 or 2), + * both strings and integers are valid values. The full list of parameters is defined at: + * https://developers.google.com/youtube/player_parameters?playerVersion=HTML5. + * + * This method reloads the entire contents of the UIWebView and regenerates its HTML contents. + * To change the currently loaded video without reloading the entire UIWebView, use the + * YTPlayerView::cueVideoById:startSeconds:suggestedQuality: family of methods. + * + * @param videoId The YouTube video ID of the video to load in the player view. + * @param playerVars An NSDictionary of player parameters. + * @return YES if player has been configured correctly, NO otherwise. + */ +- (BOOL)loadWithVideoId:(nonnull NSString *)videoId playerVars:(nullable NSDictionary *)playerVars; + +/** + * This method loads the player with the given playlist ID and player variables. Player variables + * specify optional parameters for video playback. For instance, to play a YouTube + * video inline, the following playerVars dictionary would be used: + * + * @code + * @{ @"playsinline" : @1 }; + * @endcode + * + * Note that when the documentation specifies a valid value as a number (typically 0, 1 or 2), + * both strings and integers are valid values. The full list of parameters is defined at: + * https://developers.google.com/youtube/player_parameters?playerVersion=HTML5. + * + * This method reloads the entire contents of the UIWebView and regenerates its HTML contents. + * To change the currently loaded video without reloading the entire UIWebView, use the + * YTPlayerView::cuePlaylistByPlaylistId:index:startSeconds:suggestedQuality: + * family of methods. + * + * @param playlistId The YouTube playlist ID of the playlist to load in the player view. + * @param playerVars An NSDictionary of player parameters. + * @return YES if player has been configured correctly, NO otherwise. + */ +- (BOOL)loadWithPlaylistId:(nonnull NSString *)playlistId playerVars:(nullable NSDictionary *)playerVars; + +/** + * This method loads an iframe player with the given player parameters. Usually you may want to use + * -loadWithVideoId:playerVars: or -loadWithPlaylistId:playerVars: instead of this method does not handle + * video_id or playlist_id at all. The full list of parameters is defined at: + * https://developers.google.com/youtube/player_parameters?playerVersion=HTML5. + * + * @param additionalPlayerParams An NSDictionary of parameters in addition to required parameters + * to instantiate the HTML5 player with. This differs depending on + * whether a single video or playlist is being loaded. + * @return YES if successful, NO if not. + */ +- (BOOL)loadWithPlayerParams:(nullable NSDictionary *)additionalPlayerParams; + +#pragma mark - Player controls + +// These methods correspond to their JavaScript equivalents as documented here: +// https://developers.google.com/youtube/iframe_api_reference#Playback_controls + +/** + * Starts or resumes playback on the loaded video. Corresponds to this method from + * the JavaScript API: + * https://developers.google.com/youtube/iframe_api_reference#playVideo + */ +- (void)playVideo; + +/** + * Pauses playback on a playing video. Corresponds to this method from + * the JavaScript API: + * https://developers.google.com/youtube/iframe_api_reference#pauseVideo + */ +- (void)pauseVideo; + +/** + * Stops playback on a playing video. Corresponds to this method from + * the JavaScript API: + * https://developers.google.com/youtube/iframe_api_reference#stopVideo + */ +- (void)stopVideo; + +/** + * Seek to a given time on a playing video. Corresponds to this method from + * the JavaScript API: + * https://developers.google.com/youtube/iframe_api_reference#seekTo + * + * @param seekToSeconds The time in seconds to seek to in the loaded video. + * @param allowSeekAhead Whether to make a new request to the server if the time is + * outside what is currently buffered. Recommended to set to YES. + */ +- (void)seekToSeconds:(float)seekToSeconds allowSeekAhead:(BOOL)allowSeekAhead; + +#pragma mark - Queuing videos + +// Queueing functions for videos. These methods correspond to their JavaScript +// equivalents as documented here: +// https://developers.google.com/youtube/iframe_api_reference#Queueing_Functions + +/** + * Cues a given video by its video ID for playback starting at the given time and with the + * suggested quality. Cueing loads a video, but does not start video playback. This method + * corresponds with its JavaScript API equivalent as documented here: + * https://developers.google.com/youtube/iframe_api_reference#cueVideoById + * + * @param videoId A video ID to cue. + * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. + * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. + */ +- (void)cueVideoById:(nonnull NSString *)videoId + startSeconds:(float)startSeconds + suggestedQuality:(YTPlaybackQuality)suggestedQuality; + +/** + * Cues a given video by its video ID for playback starting and ending at the given times + * with the suggested quality. Cueing loads a video, but does not start video playback. This + * method corresponds with its JavaScript API equivalent as documented here: + * https://developers.google.com/youtube/iframe_api_reference#cueVideoById + * + * @param videoId A video ID to cue. + * @param startSeconds Time in seconds to start the video when playVideo() is called. + * @param endSeconds Time in seconds to end the video after it begins playing. + * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. + */ +- (void)cueVideoById:(nonnull NSString *)videoId + startSeconds:(float)startSeconds + endSeconds:(float)endSeconds + suggestedQuality:(YTPlaybackQuality)suggestedQuality; + +/** + * Loads a given video by its video ID for playback starting at the given time and with the + * suggested quality. Loading a video both loads it and begins playback. This method + * corresponds with its JavaScript API equivalent as documented here: + * https://developers.google.com/youtube/iframe_api_reference#loadVideoById + * + * @param videoId A video ID to load and begin playing. + * @param startSeconds Time in seconds to start the video when it has loaded. + * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. + */ +- (void)loadVideoById:(nonnull NSString *)videoId + startSeconds:(float)startSeconds + suggestedQuality:(YTPlaybackQuality)suggestedQuality; + +/** + * Loads a given video by its video ID for playback starting and ending at the given times + * with the suggested quality. Loading a video both loads it and begins playback. This method + * corresponds with its JavaScript API equivalent as documented here: + * https://developers.google.com/youtube/iframe_api_reference#loadVideoById + * + * @param videoId A video ID to load and begin playing. + * @param startSeconds Time in seconds to start the video when it has loaded. + * @param endSeconds Time in seconds to end the video after it begins playing. + * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. + */ +- (void)loadVideoById:(nonnull NSString *)videoId + startSeconds:(float)startSeconds + endSeconds:(float)endSeconds + suggestedQuality:(YTPlaybackQuality)suggestedQuality; + +/** + * Cues a given video by its URL on YouTube.com for playback starting at the given time + * and with the suggested quality. Cueing loads a video, but does not start video playback. + * This method corresponds with its JavaScript API equivalent as documented here: + * https://developers.google.com/youtube/iframe_api_reference#cueVideoByUrl + * + * @param videoURL URL of a YouTube video to cue for playback. + * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. + * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. + */ +- (void)cueVideoByURL:(nonnull NSString *)videoURL + startSeconds:(float)startSeconds + suggestedQuality:(YTPlaybackQuality)suggestedQuality; + +/** + * Cues a given video by its URL on YouTube.com for playback starting at the given time + * and with the suggested quality. Cueing loads a video, but does not start video playback. + * This method corresponds with its JavaScript API equivalent as documented here: + * https://developers.google.com/youtube/iframe_api_reference#cueVideoByUrl + * + * @param videoURL URL of a YouTube video to cue for playback. + * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. + * @param endSeconds Time in seconds to end the video after it begins playing. + * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. + */ +- (void)cueVideoByURL:(nonnull NSString *)videoURL + startSeconds:(float)startSeconds + endSeconds:(float)endSeconds + suggestedQuality:(YTPlaybackQuality)suggestedQuality; + +/** + * Loads a given video by its video ID for playback starting at the given time + * with the suggested quality. Loading a video both loads it and begins playback. This method + * corresponds with its JavaScript API equivalent as documented here: + * https://developers.google.com/youtube/iframe_api_reference#loadVideoByUrl + * + * @param videoURL URL of a YouTube video to load and play. + * @param startSeconds Time in seconds to start the video when it has loaded. + * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. + */ +- (void)loadVideoByURL:(nonnull NSString *)videoURL + startSeconds:(float)startSeconds + suggestedQuality:(YTPlaybackQuality)suggestedQuality; + +/** + * Loads a given video by its video ID for playback starting and ending at the given times + * with the suggested quality. Loading a video both loads it and begins playback. This method + * corresponds with its JavaScript API equivalent as documented here: + * https://developers.google.com/youtube/iframe_api_reference#loadVideoByUrl + * + * @param videoURL URL of a YouTube video to load and play. + * @param startSeconds Time in seconds to start the video when it has loaded. + * @param endSeconds Time in seconds to end the video after it begins playing. + * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. + */ +- (void)loadVideoByURL:(nonnull NSString *)videoURL + startSeconds:(float)startSeconds + endSeconds:(float)endSeconds + suggestedQuality:(YTPlaybackQuality)suggestedQuality; + +#pragma mark - Queuing functions for playlists + +// Queueing functions for playlists. These methods correspond to +// the JavaScript methods defined here: +// https://developers.google.com/youtube/js_api_reference#Playlist_Queueing_Functions + +/** + * Cues a given playlist with the given ID. The |index| parameter specifies the 0-indexed + * position of the first video to play, starting at the given time and with the + * suggested quality. Cueing loads a playlist, but does not start video playback. This method + * corresponds with its JavaScript API equivalent as documented here: + * https://developers.google.com/youtube/iframe_api_reference#cuePlaylist + * + * @param playlistId Playlist ID of a YouTube playlist to cue. + * @param index A 0-indexed position specifying the first video to play. + * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. + * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. + */ +- (void)cuePlaylistByPlaylistId:(nonnull NSString *)playlistId + index:(int)index + startSeconds:(float)startSeconds + suggestedQuality:(YTPlaybackQuality)suggestedQuality; + +/** + * Cues a playlist of videos with the given video IDs. The |index| parameter specifies the + * 0-indexed position of the first video to play, starting at the given time and with the + * suggested quality. Cueing loads a playlist, but does not start video playback. This method + * corresponds with its JavaScript API equivalent as documented here: + * https://developers.google.com/youtube/iframe_api_reference#cuePlaylist + * + * @param videoIds An NSArray of video IDs to compose the playlist of. + * @param index A 0-indexed position specifying the first video to play. + * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. + * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. + */ +- (void)cuePlaylistByVideos:(nonnull NSArray *)videoIds + index:(int)index + startSeconds:(float)startSeconds + suggestedQuality:(YTPlaybackQuality)suggestedQuality; + +/** + * Loads a given playlist with the given ID. The |index| parameter specifies the 0-indexed + * position of the first video to play, starting at the given time and with the + * suggested quality. Loading a playlist starts video playback. This method + * corresponds with its JavaScript API equivalent as documented here: + * https://developers.google.com/youtube/iframe_api_reference#loadPlaylist + * + * @param playlistId Playlist ID of a YouTube playlist to cue. + * @param index A 0-indexed position specifying the first video to play. + * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. + * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. + */ +- (void)loadPlaylistByPlaylistId:(nonnull NSString *)playlistId + index:(int)index + startSeconds:(float)startSeconds + suggestedQuality:(YTPlaybackQuality)suggestedQuality; + +/** + * Loads a playlist of videos with the given video IDs. The |index| parameter specifies the + * 0-indexed position of the first video to play, starting at the given time and with the + * suggested quality. Loading a playlist starts video playback. This method + * corresponds with its JavaScript API equivalent as documented here: + * https://developers.google.com/youtube/iframe_api_reference#loadPlaylist + * + * @param videoIds An NSArray of video IDs to compose the playlist of. + * @param index A 0-indexed position specifying the first video to play. + * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. + * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. + */ +- (void)loadPlaylistByVideos:(nonnull NSArray *)videoIds + index:(int)index + startSeconds:(float)startSeconds + suggestedQuality:(YTPlaybackQuality)suggestedQuality; + +#pragma mark - Playing a video in a playlist + +// These methods correspond to the JavaScript API as defined under the +// "Playing a video in a playlist" section here: +// https://developers.google.com/youtube/iframe_api_reference#Playback_status + +/** + * Loads and plays the next video in the playlist. Corresponds to this method from + * the JavaScript API: + * https://developers.google.com/youtube/iframe_api_reference#nextVideo + */ +- (void)nextVideo; + +/** + * Loads and plays the previous video in the playlist. Corresponds to this method from + * the JavaScript API: + * https://developers.google.com/youtube/iframe_api_reference#previousVideo + */ +- (void)previousVideo; + +/** + * Loads and plays the video at the given 0-indexed position in the playlist. + * Corresponds to this method from the JavaScript API: + * https://developers.google.com/youtube/iframe_api_reference#playVideoAt + * + * @param index The 0-indexed position of the video in the playlist to load and play. + */ +- (void)playVideoAt:(int)index; + +#pragma mark - Setting the playback rate + +/** + * Gets the playback rate. The default value is 1.0, which represents a video + * playing at normal speed. Other values may include 0.25 or 0.5 for slower + * speeds, and 1.5 or 2.0 for faster speeds. This method corresponds to the + * JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate + * + * @return An integer value between 0 and 100 representing the current volume. + */ +- (float)playbackRate; + +/** + * Sets the playback rate. The default value is 1.0, which represents a video + * playing at normal speed. Other values may include 0.25 or 0.5 for slower + * speeds, and 1.5 or 2.0 for faster speeds. To fetch a list of valid values for + * this method, call YTPlayerView::getAvailablePlaybackRates. This method does not + * guarantee that the playback rate will change. + * This method corresponds to the JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate + * + * @param suggestedRate A playback rate to suggest for the player. + */ +- (void)setPlaybackRate:(float)suggestedRate; + +/** + * Gets a list of the valid playback rates, useful in conjunction with + * YTPlayerView::setPlaybackRate. This method corresponds to the + * JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate + * + * @return An NSArray containing available playback rates. nil if there is an error. + */ +- (nullable NSArray *)availablePlaybackRates; + +#pragma mark - Setting playback behavior for playlists + +/** + * Sets whether the player should loop back to the first video in the playlist + * after it has finished playing the last video. This method corresponds to the + * JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#loopPlaylist + * + * @param loop A boolean representing whether the player should loop. + */ +- (void)setLoop:(BOOL)loop; + +/** + * Sets whether the player should shuffle through the playlist. This method + * corresponds to the JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#shufflePlaylist + * + * @param shuffle A boolean representing whether the player should + * shuffle through the playlist. + */ +- (void)setShuffle:(BOOL)shuffle; + +#pragma mark - Playback status +// These methods correspond to the JavaScript methods defined here: +// https://developers.google.com/youtube/js_api_reference#Playback_status + +/** + * Returns a number between 0 and 1 that specifies the percentage of the video + * that the player shows as buffered. This method corresponds to the + * JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#getVideoLoadedFraction + * + * @return A float value between 0 and 1 representing the percentage of the video + * already loaded. + */ +- (float)videoLoadedFraction; + +/** + * Returns the state of the player. This method corresponds to the + * JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#getPlayerState + * + * @return |YTPlayerState| representing the state of the player. + */ +- (YTPlayerState)playerState; + +/** + * Returns the elapsed time in seconds since the video started playing. This + * method corresponds to the JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#getCurrentTime + * + * @return Time in seconds since the video started playing. + */ +- (float)currentTime; + +#pragma mark - Playback quality + +// Playback quality. These methods correspond to the JavaScript +// methods defined here: +// https://developers.google.com/youtube/js_api_reference#Playback_quality + +/** + * Returns the playback quality. This method corresponds to the + * JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#getPlaybackQuality + * + * @return YTPlaybackQuality representing the current playback quality. + */ +- (YTPlaybackQuality)playbackQuality; + +/** + * Suggests playback quality for the video. It is recommended to leave this setting to + * |default|. This method corresponds to the JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#setPlaybackQuality + * + * @param quality YTPlaybackQuality value to suggest for the player. + */ +- (void)setPlaybackQuality:(YTPlaybackQuality)suggestedQuality; + +/** + * Gets a list of the valid playback quality values, useful in conjunction with + * YTPlayerView::setPlaybackQuality. This method corresponds to the + * JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#getAvailableQualityLevels + * + * @return An NSArray containing available playback quality levels. Returns nil if there is an error. + */ +- (nullable NSArray *)availableQualityLevels; + +#pragma mark - Retrieving video information + +// Retrieving video information. These methods correspond to the JavaScript +// methods defined here: +// https://developers.google.com/youtube/js_api_reference#Retrieving_video_information + +/** + * Returns the duration in seconds since the video of the video. This + * method corresponds to the JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#getDuration + * + * @return Length of the video in seconds. + */ +- (NSTimeInterval)duration; + +/** + * Returns the YouTube.com URL for the video. This method corresponds + * to the JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#getVideoUrl + * + * @return The YouTube.com URL for the video. Returns nil if no video is loaded yet. + */ +- (nullable NSURL *)videoUrl; + +/** + * Returns the embed code for the current video. This method corresponds + * to the JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#getVideoEmbedCode + * + * @return The embed code for the current video. Returns nil if no video is loaded yet. + */ +- (nullable NSString *)videoEmbedCode; + +#pragma mark - Retrieving playlist information + +// Retrieving playlist information. These methods correspond to the +// JavaScript defined here: +// https://developers.google.com/youtube/js_api_reference#Retrieving_playlist_information + +/** + * Returns an ordered array of video IDs in the playlist. This method corresponds + * to the JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#getPlaylist + * + * @return An NSArray containing all the video IDs in the current playlist. |nil| on error. + */ +- (nullable NSArray *)playlist; + +/** + * Returns the 0-based index of the currently playing item in the playlist. + * This method corresponds to the JavaScript API defined here: + * https://developers.google.com/youtube/iframe_api_reference#getPlaylistIndex + * + * @return The 0-based index of the currently playing item in the playlist. + */ +- (int)playlistIndex; + +#pragma mark - Exposed for Testing + +/** + * Removes the internal web view from this player view. + * Intended to use for testing, should not be used in production code. + */ +- (void)removeWebView; + +@end diff --git a/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Headers/youtube-ios-player-helper-umbrella.h b/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Headers/youtube-ios-player-helper-umbrella.h new file mode 100644 index 0000000..2c716cb --- /dev/null +++ b/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Headers/youtube-ios-player-helper-umbrella.h @@ -0,0 +1,7 @@ +#import + +#import "YTPlayerView.h" + +FOUNDATION_EXPORT double youtube_ios_player_helperVersionNumber; +FOUNDATION_EXPORT const unsigned char youtube_ios_player_helperVersionString[]; + diff --git a/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Info.plist b/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..04e3fa64707b1bb3289985b87f66ddc135ed7554 GIT binary patch literal 834 zcmZ`$%T5$Q6s@|x5FZqSs354|15u~b%^*lz5Ql~Y2{O_RaI>dh!Zz1 z-0}nN)IEtB7w*_-{0I}`!e4M_V)f81Mq_Pm^*#5Ty65y-Ab1q(`VQ&Fuy6l?gNND< zw;wrr?05(1EOeddK6&bNPw$z&{%<>GIIkYuAe_Q#0knuL*XSdaP>N_gz_I z4@}!K=hDu@)~%dyQFBO7K5McKCS>3-KVD>YE`5h|SuK~09MYdNXZ>|) zayM41(6IS?sX~rVKV@zb(>0M=oRVI^1q&TQ=DE+k#9OfbrOeipU%t*+xVki}BH%8Y zp&l1ahji}w%6KbTlv#A0`MVac`!r5M<+5Xsln30DNt~=Phs($b1Z}F78cYN%wDs|l z;gA9k8q#NxT2aL)!wIdlY?P))O8O%6?-^BN?DojSC{@~j4casF)wv~IGqkZ+11Vfd zb*x;+8q;+3fB*ZuLk3;xX*37~)6}h%R-_5B;c2d*eqA#h-MX2I49T#rMqEh)S&Otx zvZx#tcO4aX9lgFXOfUf}wJ>fPx-n``s#8I2=msjIJIF>3dWJU98}tr+LEkapK|GGj zcnLqoO}vR;;w`+5zv6HB2mT2N0NSAs24Dz=;TBB7BX|xkU<=;Dd-wn!VH-Z7)&aa* Kk(Sl0z~^7{3kDki literal 0 HcmV?d00001 diff --git a/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Modules/module.modulemap b/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Modules/module.modulemap new file mode 100644 index 0000000..e25ad85 --- /dev/null +++ b/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module youtube_ios_player_helper { + umbrella header "youtube-ios-player-helper-umbrella.h" + + export * + module * { export * } +} diff --git a/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/youtube_ios_player_helper b/Project/youtube-player-ios-example/youtube_ios_player_helper.framework/youtube_ios_player_helper new file mode 100755 index 0000000000000000000000000000000000000000..c71b64e5597e5552e3a1fddbecf38b4e7a547310 GIT binary patch literal 92400 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∨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

Jh*;Xs=Q5vMYClJqPbu}kZUIaG>z{$8 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 SJ!|rX;&5!3r%L1kf&T@MQ)5H` literal 0 HcmV?d00001