Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 263b97a4a7 | |||
| fa8f1b6ada | |||
| 4541449a45 | |||
| 02397da6f6 | |||
| 6ace420f30 | |||
| 7f1b1a0363 | |||
| 2684d26605 | |||
| 116dce71c7 | |||
| e9251c75b0 | |||
| b1eebad01b |
@@ -5,8 +5,8 @@ PODS:
|
||||
- FBSnapshotTestCase/SwiftSupport (2.1.4):
|
||||
- FBSnapshotTestCase/Core
|
||||
- GoogleAds-IMA-iOS-SDK (3.7.3)
|
||||
- VersaPlayer (0.1.0)
|
||||
- VersaPlayerAdsExtension (0.1.0):
|
||||
- VersaPlayer (2.1.3)
|
||||
- VersaPlayerAdsExtension (0.5.1):
|
||||
- GoogleAds-IMA-iOS-SDK
|
||||
- VersaPlayer
|
||||
|
||||
@@ -29,9 +29,9 @@ EXTERNAL SOURCES:
|
||||
SPEC CHECKSUMS:
|
||||
FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a
|
||||
GoogleAds-IMA-iOS-SDK: 6dc764910309dcc982d6eb8b7de19d3cf94a6857
|
||||
VersaPlayer: f31313a03e3d243959df88fa726754180ea68dac
|
||||
VersaPlayerAdsExtension: 88bf285687e4069915e5228ac75fad1c8aa44d11
|
||||
VersaPlayer: 544b1bd4f4faac6bd53d8e4b5ee0461da4f273e5
|
||||
VersaPlayerAdsExtension: edbac70440e728d806d427c9869546163cfe6956
|
||||
|
||||
PODFILE CHECKSUM: 4421772df42864a0ee29f9f0ad1ed63a0f1a6d75
|
||||
|
||||
COCOAPODS: 1.5.3
|
||||
COCOAPODS: 1.6.0.beta.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "VersaPlayerAdsExtension",
|
||||
"version": "0.1.0",
|
||||
"version": "0.5.1",
|
||||
"summary": "VersaPlayer Extension to enable video ads.",
|
||||
"description": "VersaPlayer Extension to enable video ads functionality.",
|
||||
"homepage": "https://github.com/josejuanqm/VersaPlayerAdsExtension",
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"source": {
|
||||
"git": "https://github.com/josejuanqm/VersaPlayerAdsExtension.git",
|
||||
"tag": "0.1.0"
|
||||
"tag": "0.5.1"
|
||||
},
|
||||
"social_media_url": "https://twitter.com/josejuanqm",
|
||||
"platforms": {
|
||||
|
||||
Generated
+5
-5
@@ -5,8 +5,8 @@ PODS:
|
||||
- FBSnapshotTestCase/SwiftSupport (2.1.4):
|
||||
- FBSnapshotTestCase/Core
|
||||
- GoogleAds-IMA-iOS-SDK (3.7.3)
|
||||
- VersaPlayer (0.1.0)
|
||||
- VersaPlayerAdsExtension (0.1.0):
|
||||
- VersaPlayer (2.1.3)
|
||||
- VersaPlayerAdsExtension (0.5.1):
|
||||
- GoogleAds-IMA-iOS-SDK
|
||||
- VersaPlayer
|
||||
|
||||
@@ -29,9 +29,9 @@ EXTERNAL SOURCES:
|
||||
SPEC CHECKSUMS:
|
||||
FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a
|
||||
GoogleAds-IMA-iOS-SDK: 6dc764910309dcc982d6eb8b7de19d3cf94a6857
|
||||
VersaPlayer: f31313a03e3d243959df88fa726754180ea68dac
|
||||
VersaPlayerAdsExtension: 88bf285687e4069915e5228ac75fad1c8aa44d11
|
||||
VersaPlayer: 544b1bd4f4faac6bd53d8e4b5ee0461da4f273e5
|
||||
VersaPlayerAdsExtension: edbac70440e728d806d427c9869546163cfe6956
|
||||
|
||||
PODFILE CHECKSUM: 4421772df42864a0ee29f9f0ad1ed63a0f1a6d75
|
||||
|
||||
COCOAPODS: 1.5.3
|
||||
COCOAPODS: 1.6.0.beta.2
|
||||
|
||||
+803
-656
File diff suppressed because it is too large
Load Diff
+26
@@ -0,0 +1,26 @@
|
||||
<?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>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.1.4</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase
|
||||
ENABLE_BITCODE = NO
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_LDFLAGS = -framework "Foundation" -framework "QuartzCore" -framework "UIKit" -framework "XCTest"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "QuartzCore" -framework "UIKit" -framework "XCTest"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GoogleAds-IMA-iOS-SDK
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/GoogleAds-IMA-iOS-SDK"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "AdSupport" -framework "AudioToolbox" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreMedia" -framework "GoogleInteractiveMediaAds" -framework "MessageUI" -framework "QuartzCore" -framework "SystemConfiguration" -framework "UIKit" -weak_framework "WebKit"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/GoogleAds-IMA-iOS-SDK
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?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>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
+1
-1
@@ -8,7 +8,7 @@ Copyright 2015 Google, Inc. All rights reserved.
|
||||
|
||||
## VersaPlayer
|
||||
|
||||
Copyright (c) 2018 jose.juan.qm@gmail.com <jose.quintero@fox.com>
|
||||
Copyright (c) 2018 jose.juan.qm@gmail.com <jose.juan.qm@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Copyright (c) 2018 jose.juan.qm@gmail.com <jose.quintero@fox.com>
|
||||
<string>Copyright (c) 2018 jose.juan.qm@gmail.com <jose.juan.qm@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+19
-9
@@ -3,10 +3,15 @@ set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
function on_error {
|
||||
echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
|
||||
}
|
||||
trap 'on_error $LINENO' ERR
|
||||
|
||||
if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
|
||||
# If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
|
||||
# frameworks to, so exit 0 (signalling the script phase was successful).
|
||||
exit 0
|
||||
# If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
|
||||
# frameworks to, so exit 0 (signalling the script phase was successful).
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
@@ -36,8 +41,8 @@ install_framework()
|
||||
local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
if [ -L "${source}" ]; then
|
||||
echo "Symlinked..."
|
||||
source="$(readlink "${source}")"
|
||||
echo "Symlinked..."
|
||||
source="$(readlink "${source}")"
|
||||
fi
|
||||
|
||||
# Use filter instead of exclude so missing patterns don't throw errors.
|
||||
@@ -47,8 +52,13 @@ install_framework()
|
||||
local basename
|
||||
basename="$(basename -s .framework "$1")"
|
||||
binary="${destination}/${basename}.framework/${basename}"
|
||||
|
||||
if ! [ -r "$binary" ]; then
|
||||
binary="${destination}/${basename}"
|
||||
elif [ -L "${binary}" ]; then
|
||||
echo "Destination binary is symlinked..."
|
||||
dirname="$(dirname "${binary}")"
|
||||
binary="${dirname}/$(readlink "${binary}")"
|
||||
fi
|
||||
|
||||
# Strip invalid architectures so "fat" simulator / device frameworks work on device
|
||||
@@ -62,7 +72,7 @@ install_framework()
|
||||
# Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
|
||||
if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
|
||||
local swift_runtime_libs
|
||||
swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
|
||||
swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u)
|
||||
for lib in $swift_runtime_libs; do
|
||||
echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
|
||||
rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
|
||||
@@ -101,8 +111,8 @@ install_dsym() {
|
||||
|
||||
# Signs a framework with the provided identity
|
||||
code_sign_if_enabled() {
|
||||
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
|
||||
# Use the current code_sign_identitiy
|
||||
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
|
||||
# Use the current code_sign_identity
|
||||
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
|
||||
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
|
||||
|
||||
@@ -131,7 +141,7 @@ strip_invalid_archs() {
|
||||
for arch in $binary_archs; do
|
||||
if ! [[ "${ARCHS}" == *"$arch"* ]]; then
|
||||
# Strip non-valid architectures in-place
|
||||
lipo -remove "$arch" -output "$binary" "$binary" || exit 1
|
||||
lipo -remove "$arch" -output "$binary" "$binary"
|
||||
stripped="$stripped $arch"
|
||||
fi
|
||||
done
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension" "${PODS_ROOT}/GoogleAds-IMA-iOS-SDK"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer/VersaPlayer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension/VersaPlayerAdsExtension.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer/VersaPlayer.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension/VersaPlayerAdsExtension.framework/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "AdSupport" -framework "AudioToolbox" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreMedia" -framework "GoogleInteractiveMediaAds" -framework "MessageUI" -framework "QuartzCore" -framework "SystemConfiguration" -framework "UIKit" -framework "VersaPlayer" -framework "VersaPlayerAdsExtension" -weak_framework "WebKit"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension" "${PODS_ROOT}/GoogleAds-IMA-iOS-SDK"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer/VersaPlayer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension/VersaPlayerAdsExtension.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer/VersaPlayer.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension/VersaPlayerAdsExtension.framework/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "AdSupport" -framework "AudioToolbox" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreMedia" -framework "GoogleInteractiveMediaAds" -framework "MessageUI" -framework "QuartzCore" -framework "SystemConfiguration" -framework "UIKit" -framework "VersaPlayer" -framework "VersaPlayerAdsExtension" -weak_framework "WebKit"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?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>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
+1
-1
@@ -8,7 +8,7 @@ Copyright 2015 Google, Inc. All rights reserved.
|
||||
|
||||
## VersaPlayer
|
||||
|
||||
Copyright (c) 2018 jose.juan.qm@gmail.com <jose.quintero@fox.com>
|
||||
Copyright (c) 2018 jose.juan.qm@gmail.com <jose.juan.qm@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Copyright (c) 2018 jose.juan.qm@gmail.com <jose.quintero@fox.com>
|
||||
<string>Copyright (c) 2018 jose.juan.qm@gmail.com <jose.juan.qm@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+19
-9
@@ -3,10 +3,15 @@ set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
function on_error {
|
||||
echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
|
||||
}
|
||||
trap 'on_error $LINENO' ERR
|
||||
|
||||
if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
|
||||
# If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
|
||||
# frameworks to, so exit 0 (signalling the script phase was successful).
|
||||
exit 0
|
||||
# If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
|
||||
# frameworks to, so exit 0 (signalling the script phase was successful).
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
@@ -36,8 +41,8 @@ install_framework()
|
||||
local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
if [ -L "${source}" ]; then
|
||||
echo "Symlinked..."
|
||||
source="$(readlink "${source}")"
|
||||
echo "Symlinked..."
|
||||
source="$(readlink "${source}")"
|
||||
fi
|
||||
|
||||
# Use filter instead of exclude so missing patterns don't throw errors.
|
||||
@@ -47,8 +52,13 @@ install_framework()
|
||||
local basename
|
||||
basename="$(basename -s .framework "$1")"
|
||||
binary="${destination}/${basename}.framework/${basename}"
|
||||
|
||||
if ! [ -r "$binary" ]; then
|
||||
binary="${destination}/${basename}"
|
||||
elif [ -L "${binary}" ]; then
|
||||
echo "Destination binary is symlinked..."
|
||||
dirname="$(dirname "${binary}")"
|
||||
binary="${dirname}/$(readlink "${binary}")"
|
||||
fi
|
||||
|
||||
# Strip invalid architectures so "fat" simulator / device frameworks work on device
|
||||
@@ -62,7 +72,7 @@ install_framework()
|
||||
# Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
|
||||
if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
|
||||
local swift_runtime_libs
|
||||
swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
|
||||
swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u)
|
||||
for lib in $swift_runtime_libs; do
|
||||
echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
|
||||
rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
|
||||
@@ -101,8 +111,8 @@ install_dsym() {
|
||||
|
||||
# Signs a framework with the provided identity
|
||||
code_sign_if_enabled() {
|
||||
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
|
||||
# Use the current code_sign_identitiy
|
||||
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
|
||||
# Use the current code_sign_identity
|
||||
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
|
||||
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
|
||||
|
||||
@@ -131,7 +141,7 @@ strip_invalid_archs() {
|
||||
for arch in $binary_archs; do
|
||||
if ! [[ "${ARCHS}" == *"$arch"* ]]; then
|
||||
# Strip non-valid architectures in-place
|
||||
lipo -remove "$arch" -output "$binary" "$binary" || exit 1
|
||||
lipo -remove "$arch" -output "$binary" "$binary"
|
||||
stripped="$stripped $arch"
|
||||
fi
|
||||
done
|
||||
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension" "${PODS_ROOT}/GoogleAds-IMA-iOS-SDK"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension" "${PODS_ROOT}/GoogleAds-IMA-iOS-SDK"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer/VersaPlayer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension/VersaPlayerAdsExtension.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer/VersaPlayer.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer/VersaPlayer.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension/VersaPlayerAdsExtension.framework/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "AdSupport" -framework "AudioToolbox" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreMedia" -framework "FBSnapshotTestCase" -framework "GoogleInteractiveMediaAds" -framework "MessageUI" -framework "QuartzCore" -framework "SystemConfiguration" -framework "UIKit" -framework "VersaPlayer" -weak_framework "WebKit"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "AdSupport" -framework "AudioToolbox" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreMedia" -framework "FBSnapshotTestCase" -framework "Foundation" -framework "GoogleInteractiveMediaAds" -framework "MessageUI" -framework "QuartzCore" -framework "SystemConfiguration" -framework "UIKit" -framework "VersaPlayer" -framework "VersaPlayerAdsExtension" -framework "XCTest" -weak_framework "WebKit"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension" "${PODS_ROOT}/GoogleAds-IMA-iOS-SDK"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension" "${PODS_ROOT}/GoogleAds-IMA-iOS-SDK"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer/VersaPlayer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension/VersaPlayerAdsExtension.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer/VersaPlayer.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer/VersaPlayer.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension/VersaPlayerAdsExtension.framework/Headers"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "AdSupport" -framework "AudioToolbox" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreMedia" -framework "FBSnapshotTestCase" -framework "GoogleInteractiveMediaAds" -framework "MessageUI" -framework "QuartzCore" -framework "SystemConfiguration" -framework "UIKit" -framework "VersaPlayer" -weak_framework "WebKit"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "AdSupport" -framework "AudioToolbox" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreMedia" -framework "FBSnapshotTestCase" -framework "Foundation" -framework "GoogleInteractiveMediaAds" -framework "MessageUI" -framework "QuartzCore" -framework "SystemConfiguration" -framework "UIKit" -framework "VersaPlayer" -framework "VersaPlayerAdsExtension" -framework "XCTest" -weak_framework "WebKit"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1.0</string>
|
||||
<string>1.2.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.1.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,6 +1,6 @@
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1.0</string>
|
||||
<string>0.4.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
Generated
+26
@@ -0,0 +1,26 @@
|
||||
<?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>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.5.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
Generated
+3
-3
@@ -1,8 +1,8 @@
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerAdsExtension
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/GoogleAds-IMA-iOS-SDK" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_ROOT}/GoogleAds-IMA-iOS-SDK"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_LDFLAGS = -framework "GoogleInteractiveMediaAds"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
|
||||
OTHER_LDFLAGS = $(inherited) -framework "AVFoundation" -framework "AdSupport" -framework "AudioToolbox" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreMedia" -framework "GoogleInteractiveMediaAds" -framework "MessageUI" -framework "QuartzCore" -framework "SystemConfiguration" -framework "UIKit" -framework "VersaPlayer" -weak_framework "WebKit"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
|
||||
Generated
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018 jose.juan.qm@gmail.com <jose.quintero@fox.com>
|
||||
Copyright (c) 2018 jose.juan.qm@gmail.com <jose.juan.qm@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
Generated
+238
-10
@@ -9,10 +9,92 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#example">Example</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#installation">Installation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#usage">Usage</a>
|
||||
</li>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#basic-usage">Basic Usage</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#adding-controls">Adding Controls</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#advanced-usage">Advanced Usage</a>
|
||||
</li>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#drm">Encrypted Content (Digital Rights Management)</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#tracks">Track Selection</a>
|
||||
</li>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#audio-tracks">Audio Tracks</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#video-tracks">Video Tracks</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#caption-tracks">Caption Tracks</a>
|
||||
</li>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#caption-styling">Caption Styling</a>
|
||||
</li>
|
||||
</ol>
|
||||
</ol>
|
||||
</ol>
|
||||
</ol>
|
||||
<li>
|
||||
<a href="#extensions">Extensions</a>
|
||||
</li>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#extensions">Airplay Extension</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#extensions">Ads Extension</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#extensions">Overlay Content Extension</a>
|
||||
</li>
|
||||
</ol>
|
||||
<li>
|
||||
<a href="#documentation">Documentation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#contributors">Awesome People (Contributors)</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#donation">Donation</a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
## Community
|
||||
|
||||
If you have any doubts or need help with anything, head over to [Gitter](https://gitter.im/VersaPlayer/Lobby) and ask it there!
|
||||
|
||||
## Example
|
||||
|
||||
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||
|
||||
<div>
|
||||
<p align="center">
|
||||
<img src="https://github.com/josejuanqm/VersaPlayer/blob/master/RepoAssets/iphone.png" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## Installation
|
||||
|
||||
[CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects.
|
||||
@@ -35,28 +117,174 @@ pod 'VersaPlayer'
|
||||
|
||||
VersaPlayer aims to be simple to use but also flexible, to start using VersaPlayer first create a view programatically or via storyboard. Then add this few lines of code to start playing your video.
|
||||
|
||||
<div>
|
||||
<p align="center">
|
||||
<img src="https://github.com/josejuanqm/VersaPlayer/blob/master/simple_example.png" />
|
||||
</p>
|
||||
</div>
|
||||
```swift
|
||||
@IBOutlet weak var playerView: VersaPlayerView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
if let url = URL.init(string: "http://rmcdn.2mdn.net/Demo/html5/output.mp4") {
|
||||
let item = VersaPlayerItem(url: url)
|
||||
playerView.set(item: item)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Adding Controls
|
||||
|
||||
To add controls for your player use the VersaPlayerControls class, which comes packed with outlets to control your player, you can also add as many as you like by making a custom implementation.
|
||||
|
||||
<div>
|
||||
<p align="center">
|
||||
<img src="https://github.com/josejuanqm/VersaPlayer/blob/master/controls_example.png" />
|
||||
</p>
|
||||
</div>
|
||||
VersaPlayerControls class include the following outlets:
|
||||
|
||||
Outlet Name | Type | Action
|
||||
------------------------- | ------------------------- | -------------------------
|
||||
playPauseButton | VersaStatefullButton | Toggle playback
|
||||
fullscreenButton | VersaStatefullButton | Toggle fullscreen mode
|
||||
pipButton | VersaStatefullButton | Toggle PIP mode in supported devices
|
||||
rewindButton | VersaStatefullButton | Rewind playback
|
||||
forwardButton | VersaStatefullButton | Fast forward playback
|
||||
skipForwardButton | VersaStatefullButton | Skip forward the time specified in second at skipSize (found in VersaPlayerControls)
|
||||
skipBackwardButton | VersaStatefullButton | Skip backward the time specified in second at skipSize (found in VersaPlayerControls)
|
||||
seekbarSlider | VersaSeekbarSlider | Seek through playback
|
||||
currentTimeLabel | VersaTimeLabel | Indicate the current time
|
||||
totalTimeLabel | VersaTimeLabel | Indicate the total time
|
||||
bufferingView | UIView | Shown when player is buffering
|
||||
|
||||
```swift
|
||||
@IBOutlet weak var playerView: VersaPlayerView!
|
||||
@IBOutlet weak var controls: VersaPlayerControls!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
playerView.use(controls: controls)
|
||||
if let url = URL.init(string: "http://rmcdn.2mdn.net/Demo/html5/output.mp4") {
|
||||
let item = VersaPlayerItem(url: url)
|
||||
playerView.set(item: item)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
#### DRM
|
||||
|
||||
VersaPlayer also brings support for encrypted content, to make use of this funcionality you must implement VersaPlayerDecryptionDelegate and assign it to VersaPlayer's decryptionDelegate property.
|
||||
|
||||
To read more about this topic go to:
|
||||
|
||||
https://josejuanqm.github.io/Libraries-Documentation/VersaPlayerCore/Protocols/VersaPlayerDecryptionDelegate.html
|
||||
|
||||
#### Tracks
|
||||
|
||||
To make use of different media tracks, such as audio, video, or captioning, use VersaPlayerMediaTracks, found in VersaPlayer class.
|
||||
|
||||
to learn more about this properties go to:
|
||||
|
||||
https://josejuanqm.github.io/Libraries-Documentation/VersaPlayerCore/Classes/VersaPlayerMediaTrack.html
|
||||
|
||||
##### Audio Tracks
|
||||
|
||||
Audio tracks are specially helpfull when dealing with different languages, for example for a movie playback.
|
||||
|
||||
To select an audio track simply fetch available tracks with VersaPlayer's audioTracks property.
|
||||
|
||||
```swift
|
||||
@IBOutlet weak var playerView: VersaPlayer!
|
||||
|
||||
...
|
||||
|
||||
let tracks: [VersaPlayerMediaTrack] = playerView.player.currentItem?.audioTracks
|
||||
/// the name of the track
|
||||
let name = tracks.first?.name
|
||||
/// the language of the track
|
||||
let name = tracks.first?.language
|
||||
/// selecting the first one
|
||||
tracks.first?.select(for: playerView.player)
|
||||
```
|
||||
|
||||
##### Video Tracks
|
||||
|
||||
Video tracks are most helpfull when dealing with different renditions or different streams per video quality.
|
||||
|
||||
To select an video track simply follow the same principles as an audio track.
|
||||
|
||||
```swift
|
||||
@IBOutlet weak var playerView: VersaPlayer!
|
||||
|
||||
...
|
||||
|
||||
let tracks: [VersaPlayerMediaTrack] = playerView.player.currentItem?.videoTracks
|
||||
/// the name of the track
|
||||
let name = tracks.first?.name
|
||||
/// selecting the first one
|
||||
tracks.first?.select(for: playerView.player)
|
||||
```
|
||||
|
||||
##### Caption Tracks
|
||||
|
||||
Caption tracks are almost always helpfull. This can be used from a movie playback all the way to assitive content.
|
||||
|
||||
To select an video track simply follow the same principles as video and audio tracks.
|
||||
|
||||
```swift
|
||||
@IBOutlet weak var playerView: VersaPlayer!
|
||||
|
||||
...
|
||||
|
||||
let tracks: [VersaPlayerMediaTrack] = playerView.player.currentItem?.captionTracks
|
||||
/// the name of the track
|
||||
let name = tracks.first?.name
|
||||
/// the language of the track
|
||||
let name = tracks.first?.language
|
||||
/// selecting the first one
|
||||
tracks.first?.select(for: playerView.player)
|
||||
```
|
||||
|
||||
###### Caption Styling
|
||||
|
||||
Caption styling are not usually managed by the user, but when necessary, captionStyling property from VersaPlayer comes in handy.
|
||||
|
||||
Explore all the available attributes that can be changed here:
|
||||
|
||||
https://josejuanqm.github.io/Libraries-Documentation/VersaPlayerCore/Classes/VersaPlayerCaptionStyling.html
|
||||
|
||||
## Extensions
|
||||
|
||||
Versa is aimed to be versatile, and that's why it comes with an extensions feature, that lets you customize any aspect of the player by inheriting from VersaPlayerExtension.
|
||||
|
||||
This class comes with a player attribute that points to the player instance from which is being used.
|
||||
To add an extension use the add(extension ext:) method found in https://josejuanqm.github.io/Libraries-Documentation/VersaPlayerCore/Classes/VersaPlayer.html.
|
||||
|
||||
Here are some extensions for VersaPlayer that may be useful for you.
|
||||
|
||||
1. [AirPlay Extension](https://github.com/josejuanqm/VersaPlayerAirplayExtension)
|
||||
|
||||
2. [Ads Extension](https://github.com/josejuanqm/VersaPlayerAdsExtension)
|
||||
|
||||
3. [Overlay Content Extension](https://github.com/josejuanqm/VersaPlayerOverlayContentExtension)
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation avilable https://josejuanqm.github.io/Libraries-Documentation/VersaPlayerCore/
|
||||
|
||||
## Author
|
||||
|
||||
Jose Quintero - jose.juan.qm@gmail.com
|
||||
|
||||
## Contributors
|
||||
|
||||
People that make VersaPlayer possible, Thank you!
|
||||
|
||||
<span><a href="https://github.com/josejuanqm"><img src="https://github.com/josejuanqm.png" alt="josejuanqm" width="50px"></a></span>
|
||||
<span><a href="https://github.com/pbeast"><img src="https://github.com/pbeast.png" alt="pbeast" width="50px"></a></span>
|
||||
|
||||
## Donation
|
||||
|
||||
If you like this project or has been helpful to you, you can buy me a cup of coffe :)
|
||||
Appreciate it!
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=KGX5UDWNHBRNY)
|
||||
|
||||
## License
|
||||
|
||||
VersaPlayer is available under the MIT license. See the LICENSE file for more info.
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
//
|
||||
// VPlayer.swift
|
||||
// VersaPlayer Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
open class VPlayer: AVPlayer {
|
||||
|
||||
/// Notification key to extract info
|
||||
public enum VPlayerNotificationInfoKey: String {
|
||||
case time = "VERSA_PLAYER_TIME"
|
||||
}
|
||||
|
||||
/// Notification name to post
|
||||
public enum VPlayerNotificationName: String {
|
||||
case assetLoaded = "VERSA_ASSET_ADDED"
|
||||
case timeChanged = "VERSA_TIME_CHANGED"
|
||||
case willPlay = "VERSA_PLAYER_STATE_WILL_PLAY"
|
||||
case play = "VERSA_PLAYER_STATE_PLAY"
|
||||
case pause = "VERSA_PLAYER_STATE_PAUSE"
|
||||
case buffering = "VERSA_PLAYER_BUFFERING"
|
||||
case endBuffering = "VERSA_PLAYER_END_BUFFERING"
|
||||
case didEnd = "VERSA_PLAYER_END_PLAYING"
|
||||
|
||||
/// Notification name representation
|
||||
public var notification: NSNotification.Name {
|
||||
return NSNotification.Name.init(self.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// VersaPlayer instance
|
||||
public var handler: VersaPlayer!
|
||||
|
||||
/// Whether player is buffering
|
||||
public var isBuffering: Bool = false
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemTimeJumped, object: self)
|
||||
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self)
|
||||
}
|
||||
|
||||
/// Play content
|
||||
override open func play() {
|
||||
handler.playbackDelegate?.playbackWillBegin(forPlayer: self)
|
||||
NotificationCenter.default.post(name: VPlayer.VPlayerNotificationName.willPlay.notification, object: self, userInfo: nil)
|
||||
if !(handler.playbackDelegate?.playbackShouldBegin(forPlayer: self) ?? true) {
|
||||
return
|
||||
}
|
||||
NotificationCenter.default.post(name: VPlayer.VPlayerNotificationName.play.notification, object: self, userInfo: nil)
|
||||
super.play()
|
||||
handler.playbackDelegate?.playbackDidBegin(forPlayer: self)
|
||||
}
|
||||
|
||||
/// Pause content
|
||||
override open func pause() {
|
||||
NotificationCenter.default.post(name: VPlayer.VPlayerNotificationName.pause.notification, object: self, userInfo: nil)
|
||||
super.pause()
|
||||
}
|
||||
|
||||
/// Replace current item with a new one
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: AVPlayer item instance to be added
|
||||
override open func replaceCurrentItem(with item: AVPlayerItem?) {
|
||||
super.replaceCurrentItem(with: item)
|
||||
NotificationCenter.default.post(name: VPlayer.VPlayerNotificationName.assetLoaded.notification, object: self, userInfo: nil)
|
||||
if item != nil {
|
||||
currentItem!.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .new, context: nil)
|
||||
currentItem!.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
|
||||
currentItem!.addObserver(self, forKeyPath: "playbackBufferFull", options: .new, context: nil)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension VPlayer {
|
||||
|
||||
/// Start time
|
||||
///
|
||||
/// - Returns: Player's current item start time as CMTime
|
||||
public func startTime() -> CMTime {
|
||||
guard let item = currentItem else {
|
||||
return CMTime(seconds: 0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
}
|
||||
|
||||
if item.reversePlaybackEndTime.isValid {
|
||||
return item.reversePlaybackEndTime
|
||||
}else {
|
||||
return CMTime(seconds: 0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
}
|
||||
}
|
||||
|
||||
/// End time
|
||||
///
|
||||
/// - Returns: Player's current item end time as CMTime
|
||||
public func endTime() -> CMTime {
|
||||
guard let item = currentItem else {
|
||||
return CMTime(seconds: 0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
}
|
||||
|
||||
if item.forwardPlaybackEndTime.isValid {
|
||||
return item.forwardPlaybackEndTime
|
||||
}else {
|
||||
if item.duration.isValid && !item.duration.isIndefinite {
|
||||
return item.duration
|
||||
}else {
|
||||
return CMTime(seconds: 0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare players playback delegate observers
|
||||
public func preparePlayerPlaybackDelegate() {
|
||||
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self, queue: OperationQueue.main) { (notification) in
|
||||
NotificationCenter.default.post(name: VPlayer.VPlayerNotificationName.didEnd.notification, object: self, userInfo: nil)
|
||||
self.handler.playbackDelegate?.playbackDidEnd(forPlayer: self)
|
||||
}
|
||||
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemTimeJumped, object: self, queue: OperationQueue.main) { (notification) in
|
||||
self.handler.playbackDelegate?.playbackDidJump(forPlayer: self)
|
||||
}
|
||||
addPeriodicTimeObserver(
|
||||
forInterval: CMTime(
|
||||
seconds: 1,
|
||||
preferredTimescale: CMTimeScale(NSEC_PER_SEC)
|
||||
),
|
||||
queue: DispatchQueue.main) { (time) in
|
||||
NotificationCenter.default.post(name: VPlayer.VPlayerNotificationName.timeChanged.notification, object: self, userInfo: [VPlayerNotificationInfoKey.time.rawValue: time])
|
||||
self.handler.playbackDelegate?.timeDidChange(forPlayer: self, to: time)
|
||||
|
||||
}
|
||||
addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)
|
||||
}
|
||||
|
||||
/// Value observer
|
||||
override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
if let obj = object as? VPlayer, obj == self {
|
||||
if keyPath == "status" {
|
||||
switch status {
|
||||
case AVPlayerStatus.readyToPlay:
|
||||
handler.playbackDelegate?.playbackReady(forPlayer: self)
|
||||
case AVPlayerStatus.failed:
|
||||
handler.playbackDelegate?.playerDidFailToStart(forPlayer: self)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else {
|
||||
switch keyPath ?? "" {
|
||||
case "playbackBufferEmpty":
|
||||
isBuffering = true
|
||||
NotificationCenter.default.post(name: VPlayer.VPlayerNotificationName.buffering.notification, object: self, userInfo: nil)
|
||||
handler.playbackDelegate?.startBuffering(forPlayer: self)
|
||||
case "playbackLikelyToKeepUp":
|
||||
isBuffering = false
|
||||
NotificationCenter.default.post(name: VPlayer.VPlayerNotificationName.endBuffering.notification, object: self, userInfo: nil)
|
||||
handler.playbackDelegate?.endBuffering(forPlayer: self)
|
||||
case "playbackBufferFull":
|
||||
isBuffering = false
|
||||
NotificationCenter.default.post(name: VPlayer.VPlayerNotificationName.endBuffering.notification, object: self, userInfo: nil)
|
||||
handler.playbackDelegate?.endBuffering(forPlayer: self)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// VPlayerItem.swift
|
||||
// VersaPlayer Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
open class VPlayerItem: AVPlayerItem {
|
||||
|
||||
}
|
||||
Generated
Executable
+24
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// VersaPlayerPlaybackError.swift
|
||||
// VersaPlayer
|
||||
//
|
||||
// Created by Jose Quintero on 10/23/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum VersaPlayerPlaybackError {
|
||||
case unknown
|
||||
case notFound
|
||||
case unauthorized
|
||||
case authenticationError
|
||||
case forbidden
|
||||
case unavailable
|
||||
case mediaFileError
|
||||
case bandwidthExceeded
|
||||
case playlistUnchanged
|
||||
case wrongHostIP
|
||||
case wrongHostDNS
|
||||
case badURL
|
||||
case invalidRequest
|
||||
}
|
||||
Generated
Executable
+276
@@ -0,0 +1,276 @@
|
||||
//
|
||||
// VersaPlayer.swift
|
||||
// VersaPlayer Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
open class VersaPlayer: AVPlayer, AVAssetResourceLoaderDelegate {
|
||||
|
||||
/// Dispatch queue for resource loader
|
||||
private let queue = DispatchQueue(label: "quasar.studio.versaplayer")
|
||||
|
||||
/// Notification key to extract info
|
||||
public enum VPlayerNotificationInfoKey: String {
|
||||
case time = "VERSA_PLAYER_TIME"
|
||||
}
|
||||
|
||||
/// Notification name to post
|
||||
public enum VPlayerNotificationName: String {
|
||||
case assetLoaded = "VERSA_ASSET_ADDED"
|
||||
case timeChanged = "VERSA_TIME_CHANGED"
|
||||
case willPlay = "VERSA_PLAYER_STATE_WILL_PLAY"
|
||||
case play = "VERSA_PLAYER_STATE_PLAY"
|
||||
case pause = "VERSA_PLAYER_STATE_PAUSE"
|
||||
case buffering = "VERSA_PLAYER_BUFFERING"
|
||||
case endBuffering = "VERSA_PLAYER_END_BUFFERING"
|
||||
case didEnd = "VERSA_PLAYER_END_PLAYING"
|
||||
|
||||
/// Notification name representation
|
||||
public var notification: NSNotification.Name {
|
||||
return NSNotification.Name.init(self.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// VersaPlayer instance
|
||||
public var handler: VersaPlayerView!
|
||||
|
||||
/// Caption text style rules
|
||||
lazy public var captionStyling: VersaPlayerCaptionStyling = {
|
||||
return VersaPlayerCaptionStyling(with: self)
|
||||
}()
|
||||
|
||||
/// Whether player is buffering
|
||||
public var isBuffering: Bool = false
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemTimeJumped, object: self)
|
||||
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self)
|
||||
}
|
||||
|
||||
/// Play content
|
||||
override open func play() {
|
||||
handler.playbackDelegate?.playbackWillBegin(player: self)
|
||||
NotificationCenter.default.post(name: VersaPlayer.VPlayerNotificationName.willPlay.notification, object: self, userInfo: nil)
|
||||
if !(handler.playbackDelegate?.playbackShouldBegin(player: self) ?? true) {
|
||||
return
|
||||
}
|
||||
NotificationCenter.default.post(name: VersaPlayer.VPlayerNotificationName.play.notification, object: self, userInfo: nil)
|
||||
super.play()
|
||||
handler.playbackDelegate?.playbackDidBegin(player: self)
|
||||
}
|
||||
|
||||
/// Pause content
|
||||
override open func pause() {
|
||||
NotificationCenter.default.post(name: VersaPlayer.VPlayerNotificationName.pause.notification, object: self, userInfo: nil)
|
||||
super.pause()
|
||||
}
|
||||
|
||||
/// Replace current item with a new one
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: AVPlayer item instance to be added
|
||||
override open func replaceCurrentItem(with item: AVPlayerItem?) {
|
||||
if let asset = item?.asset as? AVURLAsset, let vitem = item as? VersaPlayerItem, vitem.isEncrypted {
|
||||
asset.resourceLoader.setDelegate(self, queue: queue)
|
||||
}
|
||||
|
||||
if currentItem != nil {
|
||||
currentItem!.removeObserver(self, forKeyPath: "playbackBufferEmpty")
|
||||
currentItem!.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
|
||||
currentItem!.removeObserver(self, forKeyPath: "playbackBufferFull")
|
||||
currentItem!.removeObserver(self, forKeyPath: "status")
|
||||
}
|
||||
|
||||
super.replaceCurrentItem(with: item)
|
||||
NotificationCenter.default.post(name: VersaPlayer.VPlayerNotificationName.assetLoaded.notification, object: self, userInfo: nil)
|
||||
if item != nil {
|
||||
currentItem!.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .new, context: nil)
|
||||
currentItem!.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
|
||||
currentItem!.addObserver(self, forKeyPath: "playbackBufferFull", options: .new, context: nil)
|
||||
currentItem!.addObserver(self, forKeyPath: "status", options: .new, context: nil)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension VersaPlayer {
|
||||
|
||||
/// Start time
|
||||
///
|
||||
/// - Returns: Player's current item start time as CMTime
|
||||
open func startTime() -> CMTime {
|
||||
guard let item = currentItem else {
|
||||
return CMTime(seconds: 0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
}
|
||||
|
||||
if item.reversePlaybackEndTime.isValid {
|
||||
return item.reversePlaybackEndTime
|
||||
}else {
|
||||
return CMTime(seconds: 0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
}
|
||||
}
|
||||
|
||||
/// End time
|
||||
///
|
||||
/// - Returns: Player's current item end time as CMTime
|
||||
open func endTime() -> CMTime {
|
||||
guard let item = currentItem else {
|
||||
return CMTime(seconds: 0, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
}
|
||||
|
||||
if item.forwardPlaybackEndTime.isValid {
|
||||
return item.forwardPlaybackEndTime
|
||||
}else {
|
||||
if item.duration.isValid && !item.duration.isIndefinite {
|
||||
return item.duration
|
||||
}else {
|
||||
return item.currentTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare players playback delegate observers
|
||||
open func preparePlayerPlaybackDelegate() {
|
||||
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
NotificationCenter.default.post(name: VersaPlayer.VPlayerNotificationName.didEnd.notification, object: self, userInfo: nil)
|
||||
self.handler.playbackDelegate?.playbackDidEnd(player: self)
|
||||
}
|
||||
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemTimeJumped, object: self, queue: OperationQueue.main) { (notification) in
|
||||
self.handler.playbackDelegate?.playbackDidJump(player: self)
|
||||
}
|
||||
addPeriodicTimeObserver(
|
||||
forInterval: CMTime(
|
||||
seconds: 1,
|
||||
preferredTimescale: CMTimeScale(NSEC_PER_SEC)
|
||||
),
|
||||
queue: DispatchQueue.main) { (time) in
|
||||
NotificationCenter.default.post(name: VersaPlayer.VPlayerNotificationName.timeChanged.notification, object: self, userInfo: [VPlayerNotificationInfoKey.time.rawValue: time])
|
||||
self.handler.playbackDelegate?.timeDidChange(player: self, to: time)
|
||||
|
||||
}
|
||||
addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)
|
||||
}
|
||||
|
||||
/// Value observer
|
||||
override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
if let obj = object as? VersaPlayer, obj == self {
|
||||
if keyPath == "status" {
|
||||
switch status {
|
||||
case AVPlayer.Status.readyToPlay:
|
||||
handler.playbackDelegate?.playbackReady(player: self)
|
||||
case AVPlayer.Status.failed:
|
||||
handler.playbackDelegate?.playbackDidFailed(with: VersaPlayerPlaybackError.unknown)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else {
|
||||
switch keyPath ?? "" {
|
||||
case "status":
|
||||
if let value = change?[.newKey] as? Int, let status = AVPlayerItem.Status(rawValue: value), let item = object as? AVPlayerItem {
|
||||
if status == .failed, let error = item.error as NSError?, let underlyingError = error.userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||
var playbackError = VersaPlayerPlaybackError.unknown
|
||||
switch underlyingError.code {
|
||||
case -12937:
|
||||
playbackError = .authenticationError
|
||||
case -16840:
|
||||
playbackError = .unauthorized
|
||||
case -12660:
|
||||
playbackError = .forbidden
|
||||
case -12938:
|
||||
playbackError = .notFound
|
||||
case -12661:
|
||||
playbackError = .unavailable
|
||||
case -12645, -12889:
|
||||
playbackError = .mediaFileError
|
||||
case -12318:
|
||||
playbackError = .bandwidthExceeded
|
||||
case -12642:
|
||||
playbackError = .playlistUnchanged
|
||||
case -1004:
|
||||
playbackError = .wrongHostIP
|
||||
case -1003:
|
||||
playbackError = .wrongHostDNS
|
||||
case -1000:
|
||||
playbackError = .badURL
|
||||
case -1202:
|
||||
playbackError = .invalidRequest
|
||||
default:
|
||||
playbackError = .unknown
|
||||
}
|
||||
handler.playbackDelegate?.playbackDidFailed(with: playbackError)
|
||||
}
|
||||
}
|
||||
case "playbackBufferEmpty":
|
||||
isBuffering = true
|
||||
NotificationCenter.default.post(name: VersaPlayer.VPlayerNotificationName.buffering.notification, object: self, userInfo: nil)
|
||||
handler.playbackDelegate?.startBuffering(layer: self)
|
||||
case "playbackLikelyToKeepUp":
|
||||
isBuffering = false
|
||||
NotificationCenter.default.post(name: VersaPlayer.VPlayerNotificationName.endBuffering.notification, object: self, userInfo: nil)
|
||||
handler.playbackDelegate?.endBuffering(player: self)
|
||||
case "playbackBufferFull":
|
||||
isBuffering = false
|
||||
NotificationCenter.default.post(name: VersaPlayer.VPlayerNotificationName.endBuffering.notification, object: self, userInfo: nil)
|
||||
handler.playbackDelegate?.endBuffering(player: self)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
|
||||
guard let url = loadingRequest.request.url else {
|
||||
print("VersaPlayerResourceLoadingError", #function, "Unable to read the url/host data.")
|
||||
loadingRequest.finishLoading(with: NSError(domain: "quasar.studio.error", code: -1, userInfo: nil))
|
||||
return false
|
||||
}
|
||||
|
||||
print("VersaPlayerResourceLoading: \(url)")
|
||||
|
||||
guard
|
||||
let certificateURL = handler.decryptionDelegate?.urlFor(player: self),
|
||||
let certificateData = try? Data(contentsOf: certificateURL) else {
|
||||
print("VersaPlayerResourceLoadingError", #function, "Unable to read the certificate data.")
|
||||
loadingRequest.finishLoading(with: NSError(domain: "quasar.studio.error", code: -2, userInfo: nil))
|
||||
return false
|
||||
}
|
||||
|
||||
let contentId = handler.decryptionDelegate?.contentIdFor(player: self) ?? ""
|
||||
guard
|
||||
let contentIdData = contentId.data(using: String.Encoding.utf8),
|
||||
let spcData = try? loadingRequest.streamingContentKeyRequestData(forApp: certificateData, contentIdentifier: contentIdData, options: nil),
|
||||
let dataRequest = loadingRequest.dataRequest else {
|
||||
loadingRequest.finishLoading(with: NSError(domain: "quasar.studio.error", code: -3, userInfo: nil))
|
||||
print("VersaPlayerResourceLoadingError", #function, "Unable to read the SPC data.")
|
||||
return false
|
||||
}
|
||||
|
||||
guard let ckcURL = handler.decryptionDelegate?.contentKeyContextURLFor(player: self) else {
|
||||
loadingRequest.finishLoading(with: NSError(domain: "quasar.studio.error", code: -4, userInfo: nil))
|
||||
print("VersaPlayerResourceLoadingError", #function, "Unable to read the ckcURL.")
|
||||
return false
|
||||
}
|
||||
var request = URLRequest(url: ckcURL)
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = spcData
|
||||
let session = URLSession(configuration: URLSessionConfiguration.default)
|
||||
let task = session.dataTask(with: request) { data, response, error in
|
||||
if let data = data {
|
||||
dataRequest.respond(with: data)
|
||||
loadingRequest.finishLoading()
|
||||
} else {
|
||||
print("VersaPlayerResourceLoadingError", #function, "Unable to fetch the CKC.")
|
||||
loadingRequest.finishLoading(with: NSError(domain: "quasar.studio.error", code: -5, userInfo: nil))
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
Generated
Executable
+142
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// VersaPlayerCaptionStyling.swift
|
||||
// VersaPlayer
|
||||
//
|
||||
// Created by Jose Quintero on 10/31/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
public class VersaPlayerCaptionStyling {
|
||||
|
||||
var player: VersaPlayer
|
||||
var rules: [AVTextStyleRule] = []
|
||||
|
||||
init(with player: VersaPlayer) {
|
||||
self.player = player
|
||||
}
|
||||
|
||||
/// Set attribute
|
||||
public func set(attribute: CFString, value: Any, selector: String? = nil) {
|
||||
guard let style = AVTextStyleRule.init(textMarkupAttributes: [attribute as String : value], textSelector: selector) else {
|
||||
return
|
||||
}
|
||||
rules.append(style)
|
||||
player.currentItem?.textStyleRules = rules
|
||||
}
|
||||
|
||||
/// Remove all previously set attribute
|
||||
public func clearAttributes() {
|
||||
rules = []
|
||||
player.currentItem?.textStyleRules = rules
|
||||
}
|
||||
|
||||
/// Remove any previously set attribute
|
||||
public func remove(attribute: CFString) {
|
||||
player.currentItem?.textStyleRules?.removeAll(where: { (rule) -> Bool in
|
||||
return rule.textMarkupAttributes.contains(where: { (key, value) -> Bool in
|
||||
return key == attribute as String
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Value must be one of the CFString constants in Alignment Type Constants indicating the alignment in the writing direction of the first line of text of the cue. The writing direction is indicated by the value (or absence) of the kCMTextMarkupAttribute_VerticalLayout attribute. The default value of this attribute is kCMTextMarkupAlignmentType_Middle.
|
||||
/// If used, this attribute must be applied to the entire attributed string.
|
||||
public func set(alignment value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_Alignment
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be a CFBoolean. The default is kCFBooleanFalse. If this attribute is kCFBooleanTrue, the text will be drawn with a bold style. Other styles such as italic may or may not be used as well.
|
||||
public func set(boldStyle value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_BoldStyle
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be a CFBoolean. The default is kCFBooleanFalse. If this attribute is kCFBooleanTrue, the text will be rendered with an italic style. Other styles such as bold may or may not be used as well.
|
||||
public func set(italicStyle value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_ItalicStyle
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be a CFString holding the family name of an installed font (for example, "Helvetica") that is used to render and/or measure text.
|
||||
/// When vended by legible output, an attributed string will have at most one of kCMTextMarkupAttribute_FontFamilyName or kCMTextMarkupAttribute_GenericFontFamilyName associated with each character.
|
||||
public func set(fontFamilyName value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_FontFamilyName
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be a CFBoolean. The default is kCFBooleanFalse. If this attribute is kCFBooleanTrue, the text will be rendered with an underline. Other styles such as bold may or may not be used as well.
|
||||
public func set(underlineStyle value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_UnderlineStyle
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be one of the CFString constants in Vertical Layout Constants indicating the progression direction for new vertical lines of text. If this attribute is present, it indicates the writing direction is vertical. The attribute value indicates whether new vertical text lines are added from left to right or from right to left. If this attribute is missing, the writing direction is horizontal.
|
||||
/// If used, this attribute must be applied to the entire attributed string.
|
||||
public func set(verticalLayout value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_VerticalLayout
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be a non-negative CFNumber. This is a number holding a percentage of the size of the calculated default font size. A value of 120 indicates 20% larger than the default font size. A value of 80 indicates 80% of the default font size. The default value of 100 indicates no size difference.
|
||||
public func set(relativeFontSize value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_RelativeFontSize
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be one of the CFString constants in Character Edge Style Constants that control the shape of the edges of drawn characters. The default value is kCMTextMarkupCharacterEdgeStyle_None.
|
||||
public func set(characterEdgeStyle value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_CharacterEdgeStyle
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be a CFArray of 4 CFNumbers representing alpha, red, green, and blue fields with values between 0.0 and 1.0. The red, green and blue components are interpreted in the sRGB color space. The alpha indicates the opacity from 0.0 for transparent to 1.0 for 100% opaque.
|
||||
/// The color applies to the geometry (for example, a box) containing the text. The container's background color may have an alpha of 0 so it is not displayed even though the text is displayed. The color behind individual characters is optionally controllable with the kCMTextMarkupAttribute_CharacterBackgroundColorARGB attribute.
|
||||
/// If used, this attribute must be applied to the entire attributed string.
|
||||
public func set(backgroundColorARGB value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_BackgroundColorARGB
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be a CFArray of 4 CFNumbers representing alpha, red, green, and blue fields with values between 0.0 and 1.0. The red, green and blue components are interpreted in the sRGB color space. The alpha indicates the opacity from 0.0 for transparent to 1.0 for 100% opaque.
|
||||
public func set(foregroundColorARGB value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_ForegroundColorARGB
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be one of the CFString constants in Generic Font Names. Generic fonts must be mapped to the family name of an installed font before rendering and/or measuring text (see Media Accessibility Function).
|
||||
/// When vended by legible output, an attributed string will have at most one of kCMTextMarkupAttribute_FontFamilyName or kCMTextMarkupAttribute_GenericFontFamilyName associated with each character.
|
||||
public func set(genericFontFamilyName value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_GenericFontFamilyName
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be a CFArray of 4 CFNumbers representing alpha, red, green, and blue fields with values between 0.0 and 1.0. The red, green and blue components are interpreted in the sRGB color space. The alpha indicates the opacity from 0.0 for transparent to 1.0 for 100% opaque.
|
||||
public func set(characterBackgroundColorARGB value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_CharacterBackgroundColorARGB
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be a non-negative CFNumber. This is a number expressing the width of the bounding box for text layout as a percentage of the video frame's dimension in the writing direction. For a horizontal writing direction, it is the width. For a vertical writing direction, it is the height.
|
||||
/// If used, this attribute must be applied to the entire attributed string.
|
||||
public func set(writingDirectionSizePercentage value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_WritingDirectionSizePercentage
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be a non-negative CFNumber. This is a number holding a percentage of the height of the video frame. For example, a value of 5 indicates that the base font size should be 5% of the height of the video.
|
||||
public func set(baseFontSizePercentageRelativeToVideoHeight value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_BaseFontSizePercentageRelativeToVideoHeight
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
/// Value must be a non-negative CFNumber. This is a number expressing the position of the center of the cue relative to the writing direction. The line position is orthogonal (or perpendicular) to the writing direction (that is, for a horizontal writing direction, it is vertical and for a vertical writing direction, is is horizontal). This attribute expresses the line position as a percentage of the dimensions of the video frame in the relevant direction. For example, 0% is the top of the video frame and 100% is the bottom of the video frame for horizontal writing layout.
|
||||
/// If used, this attribute must be applied to the entire attributed string.
|
||||
public func set(orthogonalLinePositionPercentageRelativeToWritingDirection value: Any) {
|
||||
let attribute: CFString = kCMTextMarkupAttribute_OrthogonalLinePositionPercentageRelativeToWritingDirection
|
||||
set(attribute: attribute, value: value)
|
||||
}
|
||||
|
||||
}
|
||||
Generated
Executable
+41
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// VPlayerItem.swift
|
||||
// VersaPlayer Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
open class VersaPlayerItem: AVPlayerItem {
|
||||
|
||||
/// whether content passed through the asset is encrypted and should be decrypted
|
||||
public var isEncrypted: Bool = false
|
||||
|
||||
public var audioTracks: [VersaPlayerMediaTrack] {
|
||||
return tracks(for: .audible)
|
||||
}
|
||||
|
||||
public var videoTracks: [VersaPlayerMediaTrack] {
|
||||
return tracks(for: .visual)
|
||||
}
|
||||
|
||||
public var captionTracks: [VersaPlayerMediaTrack] {
|
||||
return tracks(for: .legible)
|
||||
}
|
||||
|
||||
private func tracks(for characteristic: AVMediaCharacteristic) -> [VersaPlayerMediaTrack] {
|
||||
guard let group = asset.mediaSelectionGroup(forMediaCharacteristic: characteristic) else {
|
||||
return []
|
||||
}
|
||||
let options = group.options
|
||||
let tracks = options.map { (option) -> VersaPlayerMediaTrack in
|
||||
let title = option.displayName
|
||||
let language = option.extendedLanguageTag ?? "none"
|
||||
return VersaPlayerMediaTrack(option: option, group: group, name: title, language: language)
|
||||
}
|
||||
return tracks
|
||||
}
|
||||
|
||||
}
|
||||
Generated
Executable
+20
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// VersaPlayerMediaTrack.swift
|
||||
// VersaPlayer
|
||||
//
|
||||
// Created by Jose Quintero on 10/30/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
public struct VersaPlayerMediaTrack {
|
||||
public var option: AVMediaSelectionOption
|
||||
public var group: AVMediaSelectionGroup
|
||||
public var name: String
|
||||
public var language: String
|
||||
|
||||
public func select(for player: VersaPlayer) {
|
||||
player.currentItem?.select(option, in: group)
|
||||
}
|
||||
}
|
||||
Generated
Executable
+14
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// VersaPlayerDecryptionDelegate.swift
|
||||
// VersaPlayer
|
||||
//
|
||||
// Created by Jose Quintero on 10/23/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol VersaPlayerDecryptionDelegate {
|
||||
func urlFor(player: VersaPlayer) -> URL
|
||||
func contentIdFor(player: VersaPlayer) -> String
|
||||
func contentKeyContextURLFor(player: VersaPlayer) -> URL
|
||||
}
|
||||
Generated
Regular → Executable
+4
-4
@@ -11,19 +11,19 @@ import Foundation
|
||||
open class VersaPlayerExtension: NSObject {
|
||||
|
||||
/// VersaPlayer instance being used
|
||||
public var player: VersaPlayer
|
||||
open var player: VersaPlayerView
|
||||
|
||||
public init(with player: VersaPlayer) {
|
||||
public init(with player: VersaPlayerView) {
|
||||
self.player = player
|
||||
}
|
||||
|
||||
/// Notifies when player added the extension
|
||||
public func didAddExtension() {
|
||||
open func didAddExtension() {
|
||||
|
||||
}
|
||||
|
||||
/// Make preparations for the extension such as modifying the view
|
||||
public func prepare() {
|
||||
open func prepare() {
|
||||
|
||||
}
|
||||
}
|
||||
Generated
Regular → Executable
+24
-24
@@ -14,64 +14,64 @@ public protocol VersaPlayerPlaybackDelegate {
|
||||
/// Notifies when playback time changes
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - player: VPlayer being used
|
||||
/// - player: VersaPlayer being used
|
||||
/// - time: Current time
|
||||
func timeDidChange(forPlayer player: VPlayer, to time: CMTime)
|
||||
func timeDidChange(player: VersaPlayer, to time: CMTime)
|
||||
|
||||
/// Whether if playback should begin on specified player
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - player: VPlayer being used
|
||||
/// - player: VersaPlayer being used
|
||||
///
|
||||
/// - Returns: Boolean to validate if should play
|
||||
func playbackShouldBegin(forPlayer player: VPlayer) -> Bool
|
||||
func playbackShouldBegin(player: VersaPlayer) -> Bool
|
||||
|
||||
/// Whether if playback is skipping frames
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - player: VPlayer being used
|
||||
func playbackDidJump(forPlayer player: VPlayer)
|
||||
/// - player: VersaPlayer being used
|
||||
func playbackDidJump(player: VersaPlayer)
|
||||
|
||||
/// Notifies when player will begin playback
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - player: VPlayer being used
|
||||
func playbackWillBegin(forPlayer player: VPlayer)
|
||||
/// - player: VersaPlayer being used
|
||||
func playbackWillBegin(player: VersaPlayer)
|
||||
|
||||
/// Notifies when playback is ready to play
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - player: VPlayer being used
|
||||
func playbackReady(forPlayer player: VPlayer)
|
||||
/// - player: VersaPlayer being used
|
||||
func playbackReady(player: VersaPlayer)
|
||||
|
||||
/// Notifies when playback did begin
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - player: VPlayer being used
|
||||
func playbackDidBegin(forPlayer player: VPlayer)
|
||||
/// - player: VersaPlayer being used
|
||||
func playbackDidBegin(player: VersaPlayer)
|
||||
|
||||
/// Notifies when player ended playback
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - player: VPlayer being used
|
||||
func playbackDidEnd(forPlayer player: VPlayer)
|
||||
|
||||
/// Notifies when player failed to start playback
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - player: VPlayer being used
|
||||
func playerDidFailToStart(forPlayer player: VPlayer)
|
||||
/// - player: VersaPlayer being used
|
||||
func playbackDidEnd(player: VersaPlayer)
|
||||
|
||||
/// Notifies when player starts buffering
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - player: VPlayer being used
|
||||
func startBuffering(forPlayer: VPlayer)
|
||||
/// - player: VersaPlayer being used
|
||||
func startBuffering(layer: VersaPlayer)
|
||||
|
||||
/// Notifies when player ends buffering
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - player: VPlayer being used
|
||||
func endBuffering(forPlayer: VPlayer)
|
||||
/// - player: VersaPlayer being used
|
||||
func endBuffering(player: VersaPlayer)
|
||||
|
||||
/// Notifies when playback fails with an error
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - error: playback error
|
||||
func playbackDidFailed(with error: VersaPlayerPlaybackError)
|
||||
|
||||
}
|
||||
Generated
Regular → Executable
+7
-3
@@ -9,13 +9,13 @@
|
||||
import AVFoundation
|
||||
import AVKit
|
||||
|
||||
open class VPlayerLayer: CALayer {
|
||||
open class VersaPlayerLayer: CALayer {
|
||||
|
||||
/// Player Layer to be used
|
||||
public var playerLayer: AVPlayerLayer!
|
||||
|
||||
/// VersaPlayer instance being rendered
|
||||
public var handler: VersaPlayer!
|
||||
public var handler: VersaPlayerView!
|
||||
|
||||
override public init(layer: Any) {
|
||||
super.init(layer: layer)
|
||||
@@ -25,12 +25,16 @@ open class VPlayerLayer: CALayer {
|
||||
super.init()
|
||||
}
|
||||
|
||||
public convenience init(with player: VersaPlayer) {
|
||||
public convenience init(with player: VersaPlayerView) {
|
||||
self.init()
|
||||
playerLayer = AVPlayerLayer.init(player: player.player)
|
||||
|
||||
#if os(iOS)
|
||||
let controller = AVPictureInPictureController(playerLayer: playerLayer)
|
||||
controller?.delegate = player
|
||||
player.pipController = controller
|
||||
#endif
|
||||
|
||||
addSublayer(playerLayer)
|
||||
}
|
||||
|
||||
Generated
Regular → Executable
+27
-7
@@ -6,22 +6,26 @@
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
import AVKit
|
||||
|
||||
open class VPlayerRenderingView: UIView {
|
||||
open class VersaPlayerRenderingView: View {
|
||||
|
||||
/// VPlayerLayer instance used to render player content
|
||||
public var renderingLayer: VPlayerLayer!
|
||||
public var renderingLayer: VersaPlayerLayer!
|
||||
|
||||
/// VersaPlayer instance being rendered by renderingLayer
|
||||
public var player: VersaPlayer!
|
||||
public var player: VersaPlayerView!
|
||||
|
||||
/// Constructor
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - player: VersaPlayer instance to render.
|
||||
public init(with player: VersaPlayer) {
|
||||
public init(with player: VersaPlayerView) {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.player = player
|
||||
}
|
||||
@@ -30,14 +34,30 @@ open class VPlayerRenderingView: UIView {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override open func layoutSubviews() {
|
||||
#if os(macOS)
|
||||
|
||||
open override func layout() {
|
||||
super.layout()
|
||||
if renderingLayer == nil {
|
||||
renderingLayer = VersaPlayerLayer.init(with: player)
|
||||
layer = renderingLayer.playerLayer
|
||||
}
|
||||
|
||||
renderingLayer.playerLayer.frame = bounds
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
open override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
if renderingLayer == nil {
|
||||
renderingLayer = VPlayerLayer.init(with: player)
|
||||
renderingLayer = VersaPlayerLayer.init(with: player)
|
||||
layer.addSublayer(renderingLayer.playerLayer)
|
||||
}
|
||||
|
||||
renderingLayer.playerLayer.frame = bounds
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
Generated
Executable
+22
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// VersaSeekbarSlider.swift
|
||||
// VersaPlayer Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
public typealias Slider = NSSlider
|
||||
#elseif os(iOS)
|
||||
import UIKit
|
||||
public typealias Slider = UISlider
|
||||
#else
|
||||
import UIKit
|
||||
public typealias Slider = UIProgressView
|
||||
#endif
|
||||
|
||||
open class VersaSeekbarSlider: Slider {
|
||||
|
||||
}
|
||||
Generated
Executable
+59
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// VersaRewindButton.swift
|
||||
// VersaPlayer Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
public typealias Button = NSButton
|
||||
#else
|
||||
public typealias Button = UIButton
|
||||
#endif
|
||||
|
||||
@IBDesignable
|
||||
open class VersaStatefulButton: Button {
|
||||
|
||||
#if os(macOS)
|
||||
open override var state: NSControl.StateValue {
|
||||
didSet {
|
||||
if state == .on {
|
||||
image = activeImage
|
||||
}else {
|
||||
image = inactiveImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBInspectable public var activeImage: NSImage? = nil
|
||||
@IBInspectable public var inactiveImage: NSImage? = nil
|
||||
#else
|
||||
@IBInspectable public var activeImage: UIImage? = nil
|
||||
@IBInspectable public var inactiveImage: UIImage? = nil {
|
||||
didSet {
|
||||
setImage(inactiveImage, for: .normal)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
open func set(active: Bool) {
|
||||
#if os(macOS)
|
||||
state = active ? .on : .off
|
||||
#else
|
||||
if active {
|
||||
setImage(activeImage, for: .normal)
|
||||
}else {
|
||||
setImage(inactiveImage, for: .normal)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Generated
Regular → Executable
+17
-2
@@ -6,18 +6,33 @@
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
open class VersaTimeLabel: UILabel {
|
||||
#if os(macOS)
|
||||
public typealias TextField = NSTextField
|
||||
#else
|
||||
public typealias TextField = UITextField
|
||||
#endif
|
||||
|
||||
open class VersaTimeLabel: TextField {
|
||||
|
||||
public var timeFormat: String = "HH:mm:ss"
|
||||
|
||||
public func update(toTime: TimeInterval) {
|
||||
open func update(toTime: TimeInterval) {
|
||||
let date = Date(timeIntervalSince1970: toTime)
|
||||
let formatter = DateFormatter()
|
||||
formatter.timeZone = TimeZone.init(secondsFromGMT: 0)
|
||||
formatter.dateFormat = timeFormat
|
||||
|
||||
#if os(macOS)
|
||||
stringValue = formatter.string(from: date)
|
||||
#else
|
||||
text = formatter.string(from: date)
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
Generated
Regular → Executable
+21
-16
@@ -6,8 +6,12 @@
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
import Foundation
|
||||
|
||||
open class VersaPlayerControlsBehaviour {
|
||||
|
||||
@@ -50,7 +54,7 @@ open class VersaPlayerControlsBehaviour {
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - time: TimeInterval to check whether to update controls.
|
||||
public func update(with time: TimeInterval) {
|
||||
open func update(with time: TimeInterval) {
|
||||
elapsedTime = time
|
||||
if showingControls && shouldHideControls && !controls.handler.player.isBuffering && !controls.handler.isSeeking {
|
||||
let timediff = elapsedTime - activationTime
|
||||
@@ -61,26 +65,27 @@ open class VersaPlayerControlsBehaviour {
|
||||
}
|
||||
|
||||
/// Default activation block
|
||||
public func defaultActivationBlock() {
|
||||
open func defaultActivationBlock() {
|
||||
controls.isHidden = false
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
self.controls.alpha = 1
|
||||
})
|
||||
#if os(macOS)
|
||||
controls.alphaValue = 1
|
||||
#else
|
||||
controls.alpha = 1
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Default deactivation block
|
||||
public func defaultDeactivationBlock() {
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
self.controls.alpha = 0
|
||||
}, completion: {
|
||||
if $0 {
|
||||
self.controls.isHidden = true
|
||||
}
|
||||
})
|
||||
open func defaultDeactivationBlock() {
|
||||
controls.isHidden = true
|
||||
#if os(macOS)
|
||||
controls.alphaValue = 0
|
||||
#else
|
||||
controls.alpha = 0
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Hide the controls
|
||||
public func hide() {
|
||||
open func hide() {
|
||||
if deactivationBlock != nil {
|
||||
deactivationBlock!(controls)
|
||||
}else {
|
||||
@@ -90,7 +95,7 @@ open class VersaPlayerControlsBehaviour {
|
||||
}
|
||||
|
||||
/// Show the controls
|
||||
public func show() {
|
||||
open func show() {
|
||||
if !shouldShowControls {
|
||||
return
|
||||
}
|
||||
Generated
Executable
+284
@@ -0,0 +1,284 @@
|
||||
//
|
||||
// VersaPlayerGestureRecieverView.swift
|
||||
// VersaPlayerView Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
open class VersaPlayerGestureRecieverView: View {
|
||||
|
||||
/// VersaPlayerGestureRecieverViewDelegate instance
|
||||
public var delegate: VersaPlayerGestureRecieverViewDelegate? = nil
|
||||
|
||||
/// Single tap UITapGestureRecognizer
|
||||
public var tapGesture: NSClickGestureRecognizer? = nil
|
||||
|
||||
/// Double tap UITapGestureRecognizer
|
||||
public var doubleTapGesture: NSClickGestureRecognizer? = nil
|
||||
|
||||
/// UIPanGestureRecognizer
|
||||
public var panGesture: NSPanGestureRecognizer? = nil
|
||||
|
||||
/// UIPinchGestureRecognizer
|
||||
public var pinchGesture: NSMagnificationGestureRecognizer? = nil
|
||||
|
||||
/// Whether or not reciever view is ready
|
||||
public var ready: Bool = false
|
||||
|
||||
/// Pan gesture initial point
|
||||
public var panGestureInitialPoint: CGPoint = CGPoint.zero
|
||||
|
||||
open override func viewDidMoveToSuperview() {
|
||||
super.viewDidMoveToSuperview()
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
if let parent = superview {
|
||||
topAnchor.constraint(equalTo: parent.topAnchor).isActive = true
|
||||
leftAnchor.constraint(equalTo: parent.leftAnchor).isActive = true
|
||||
rightAnchor.constraint(equalTo: parent.rightAnchor).isActive = true
|
||||
bottomAnchor.constraint(equalTo: parent.bottomAnchor).isActive = true
|
||||
}
|
||||
if !ready {
|
||||
prepare()
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare the view gesture recognizers
|
||||
open func prepare() {
|
||||
ready = true
|
||||
tapGesture = NSClickGestureRecognizer(target: self, action: #selector(tapHandler(with:)))
|
||||
tapGesture?.numberOfClicksRequired = 1
|
||||
|
||||
doubleTapGesture = NSClickGestureRecognizer(target: self, action: #selector(doubleTapHandler(with:)))
|
||||
doubleTapGesture?.numberOfClicksRequired = 2
|
||||
|
||||
tapGesture?.shouldBeRequiredToFail(by: tapGesture!)
|
||||
|
||||
pinchGesture = NSMagnificationGestureRecognizer(target: self, action: #selector(pinchHandler(with:)))
|
||||
panGesture = NSPanGestureRecognizer(target: self, action: #selector(panHandler(with:)))
|
||||
panGesture?.numberOfTouchesRequired = 1
|
||||
|
||||
addGestureRecognizer(tapGesture!)
|
||||
addGestureRecognizer(doubleTapGesture!)
|
||||
addGestureRecognizer(panGesture!)
|
||||
addGestureRecognizer(pinchGesture!)
|
||||
}
|
||||
|
||||
|
||||
@objc open func tapHandler(with sender: NSClickGestureRecognizer) {
|
||||
delegate?.didTap(at: sender.location(in: self))
|
||||
}
|
||||
|
||||
@objc open func doubleTapHandler(with sender: NSClickGestureRecognizer) {
|
||||
delegate?.didDoubleTap(at: sender.location(in: self))
|
||||
}
|
||||
|
||||
@objc open func pinchHandler(with sender: NSMagnificationGestureRecognizer) {
|
||||
if sender.state == .ended {
|
||||
delegate?.didPinch(with: sender.magnification)
|
||||
}
|
||||
}
|
||||
|
||||
@objc open func panHandler(with sender: NSPanGestureRecognizer) {
|
||||
if sender.state == .began {
|
||||
panGestureInitialPoint = sender.location(in: self)
|
||||
}
|
||||
delegate?.didPan(with: sender.translation(in: self), initially: panGestureInitialPoint)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#elseif os(iOS)
|
||||
|
||||
open class VersaPlayerGestureRecieverView: UIView {
|
||||
|
||||
/// VersaPlayerGestureRecieverViewDelegate instance
|
||||
public var delegate: VersaPlayerGestureRecieverViewDelegate? = nil
|
||||
|
||||
/// Single tap UITapGestureRecognizer
|
||||
public var tapGesture: UITapGestureRecognizer? = nil
|
||||
|
||||
/// Double tap UITapGestureRecognizer
|
||||
public var doubleTapGesture: UITapGestureRecognizer? = nil
|
||||
|
||||
/// UIPanGestureRecognizer
|
||||
public var panGesture: UIPanGestureRecognizer? = nil
|
||||
|
||||
/// UIPinchGestureRecognizer
|
||||
public var pinchGesture: UIPinchGestureRecognizer? = nil
|
||||
|
||||
/// Whether or not reciever view is ready
|
||||
public var ready: Bool = false
|
||||
|
||||
/// Pan gesture initial point
|
||||
public var panGestureInitialPoint: CGPoint = CGPoint.zero
|
||||
|
||||
override open func didMoveToSuperview() {
|
||||
super.didMoveToSuperview()
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
if let parent = superview {
|
||||
topAnchor.constraint(equalTo: parent.topAnchor).isActive = true
|
||||
leftAnchor.constraint(equalTo: parent.leftAnchor).isActive = true
|
||||
rightAnchor.constraint(equalTo: parent.rightAnchor).isActive = true
|
||||
bottomAnchor.constraint(equalTo: parent.bottomAnchor).isActive = true
|
||||
}
|
||||
if !ready {
|
||||
prepare()
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare the view gesture recognizers
|
||||
open func prepare() {
|
||||
ready = true
|
||||
isUserInteractionEnabled = true
|
||||
tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapHandler(with:)))
|
||||
tapGesture?.numberOfTapsRequired = 1
|
||||
|
||||
doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(doubleTapHandler(with:)))
|
||||
doubleTapGesture?.numberOfTapsRequired = 2
|
||||
|
||||
tapGesture?.require(toFail: doubleTapGesture!)
|
||||
|
||||
pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinchHandler(with:)))
|
||||
panGesture = UIPanGestureRecognizer(target: self, action: #selector(panHandler(with:)))
|
||||
panGesture?.minimumNumberOfTouches = 1
|
||||
|
||||
addGestureRecognizer(tapGesture!)
|
||||
addGestureRecognizer(doubleTapGesture!)
|
||||
addGestureRecognizer(panGesture!)
|
||||
addGestureRecognizer(pinchGesture!)
|
||||
}
|
||||
|
||||
|
||||
@objc open func tapHandler(with sender: UITapGestureRecognizer) {
|
||||
delegate?.didTap(at: sender.location(in: self))
|
||||
}
|
||||
|
||||
@objc open func doubleTapHandler(with sender: UITapGestureRecognizer) {
|
||||
delegate?.didDoubleTap(at: sender.location(in: self))
|
||||
}
|
||||
|
||||
@objc open func pinchHandler(with sender: UIPinchGestureRecognizer) {
|
||||
if sender.state == .ended {
|
||||
delegate?.didPinch(with: sender.scale)
|
||||
}
|
||||
}
|
||||
|
||||
@objc open func panHandler(with sender: UIPanGestureRecognizer) {
|
||||
if sender.state == .began {
|
||||
panGestureInitialPoint = sender.location(in: self)
|
||||
}
|
||||
delegate?.didPan(with: sender.translation(in: self), initially: panGestureInitialPoint)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
open class VersaPlayerGestureRecieverView: UIView {
|
||||
|
||||
internal var handler: VersaPlayerView!
|
||||
|
||||
/// VersaPlayerGestureRecieverViewDelegate instance
|
||||
public var delegate: VersaPlayerGestureRecieverViewDelegate? = nil
|
||||
|
||||
/// UITapGestureRecognizer
|
||||
public var tapGesture: UITapGestureRecognizer? = nil
|
||||
|
||||
/// UIPanGestureRecognizer
|
||||
public var swipeGestureUp: UISwipeGestureRecognizer? = nil
|
||||
public var swipeGestureDown: UISwipeGestureRecognizer? = nil
|
||||
public var swipeGestureLeft: UISwipeGestureRecognizer? = nil
|
||||
public var swipeGestureRight: UISwipeGestureRecognizer? = nil
|
||||
|
||||
/// Whether or not reciever view is ready
|
||||
public var ready: Bool = false
|
||||
|
||||
/// Should become focused
|
||||
public var shouldBecomeFocused: Bool = true
|
||||
|
||||
private var initialSwipeLocation: CGPoint = .zero
|
||||
|
||||
open override var canBecomeFocused: Bool {
|
||||
return shouldBecomeFocused
|
||||
}
|
||||
|
||||
open override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
|
||||
super.didUpdateFocus(in: context, with: coordinator)
|
||||
}
|
||||
|
||||
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
initialSwipeLocation = touches.first?.location(in: self) ?? .zero
|
||||
}
|
||||
|
||||
override open func didMoveToSuperview() {
|
||||
super.didMoveToSuperview()
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
if let parent = superview {
|
||||
topAnchor.constraint(equalTo: parent.topAnchor).isActive = true
|
||||
leftAnchor.constraint(equalTo: parent.leftAnchor).isActive = true
|
||||
rightAnchor.constraint(equalTo: parent.rightAnchor).isActive = true
|
||||
bottomAnchor.constraint(equalTo: parent.bottomAnchor).isActive = true
|
||||
}
|
||||
if !ready {
|
||||
prepare()
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare the view gesture recognizers
|
||||
public func prepare() {
|
||||
ready = true
|
||||
isUserInteractionEnabled = true
|
||||
tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapHandler(with:)))
|
||||
tapGesture?.allowedPressTypes = [NSNumber(value: UIPress.PressType.menu.rawValue), NSNumber(value: UIPress.PressType.select.rawValue)]
|
||||
tapGesture?.numberOfTapsRequired = 1
|
||||
|
||||
let playPause = UITapGestureRecognizer(target: self, action: #selector(togglePlayback))
|
||||
playPause.allowedPressTypes = [NSNumber(value: UIPress.PressType.playPause.rawValue)]
|
||||
playPause.numberOfTapsRequired = 1
|
||||
|
||||
swipeGestureUp = UISwipeGestureRecognizer(target: self, action: #selector(swipeHandler(with:)))
|
||||
swipeGestureUp?.direction = UISwipeGestureRecognizer.Direction.up
|
||||
|
||||
swipeGestureDown = UISwipeGestureRecognizer(target: self, action: #selector(swipeHandler(with:)))
|
||||
swipeGestureDown?.direction = UISwipeGestureRecognizer.Direction.down
|
||||
|
||||
swipeGestureLeft = UISwipeGestureRecognizer(target: self, action: #selector(swipeHandler(with:)))
|
||||
swipeGestureLeft?.direction = UISwipeGestureRecognizer.Direction.left
|
||||
|
||||
swipeGestureRight = UISwipeGestureRecognizer(target: self, action: #selector(swipeHandler(with:)))
|
||||
swipeGestureRight?.direction = UISwipeGestureRecognizer.Direction.right
|
||||
|
||||
addGestureRecognizer(tapGesture!)
|
||||
addGestureRecognizer(playPause)
|
||||
addGestureRecognizer(swipeGestureUp!)
|
||||
addGestureRecognizer(swipeGestureDown!)
|
||||
addGestureRecognizer(swipeGestureLeft!)
|
||||
addGestureRecognizer(swipeGestureRight!)
|
||||
}
|
||||
|
||||
@objc private func togglePlayback() {
|
||||
self.handler.togglePlayback()
|
||||
}
|
||||
|
||||
@objc public func tapHandler(with sender: UITapGestureRecognizer) {
|
||||
delegate?.didTap(at: sender.location(in: self))
|
||||
}
|
||||
|
||||
@objc public func swipeHandler(with sender: UISwipeGestureRecognizer) {
|
||||
let direction: UISwipeGestureRecognizer.Direction = sender.direction
|
||||
delegate?.didSwipe(with: direction)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Generated
Regular → Executable
+21
-3
@@ -1,13 +1,17 @@
|
||||
//
|
||||
// VersaPlayerGestureRecieverViewDelegate.swift
|
||||
// VersaPlayer Demo
|
||||
// VersaPlayerView Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
import Foundation
|
||||
|
||||
public protocol VersaPlayerGestureRecieverViewDelegate {
|
||||
|
||||
@@ -20,13 +24,27 @@ public protocol VersaPlayerGestureRecieverViewDelegate {
|
||||
/// Tap was recognized
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - scale: CGPoin at wich touch was recognized
|
||||
/// - point: CGPoint at wich touch was recognized
|
||||
func didTap(at point: CGPoint)
|
||||
|
||||
/// Double tap was recognized
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - point: CGPoint at wich touch was recognized
|
||||
func didDoubleTap(at point: CGPoint)
|
||||
|
||||
/// Pan was recognized
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - translation: translation in view
|
||||
/// - at: initial point recognized
|
||||
func didPan(with translation: CGPoint, initially at: CGPoint)
|
||||
|
||||
#if os(tvOS)
|
||||
/// Swipe was recognized
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - direction: gestureDirection
|
||||
func didSwipe(with direction: UISwipeGestureRecognizer.Direction)
|
||||
#endif
|
||||
}
|
||||
Generated
Regular → Executable
+153
-51
@@ -6,14 +6,18 @@
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
import AVFoundation
|
||||
import AVKit
|
||||
|
||||
open class VersaPlayerControls: UIView {
|
||||
open class VersaPlayerControls: View {
|
||||
|
||||
/// VersaPlayer intance being controlled
|
||||
public var handler: VersaPlayer!
|
||||
public var handler: VersaPlayerView!
|
||||
|
||||
/// VersaPlayerControlsBehaviour being used to validate ui
|
||||
public var behaviour: VersaPlayerControlsBehaviour!
|
||||
@@ -27,8 +31,10 @@ open class VersaPlayerControls: UIView {
|
||||
/// VersaStatefulButton instance to represent the fullscreen toggle button
|
||||
@IBOutlet public weak var fullscreenButton: VersaStatefulButton? = nil
|
||||
|
||||
#if os(iOS)
|
||||
/// VersaStatefulButton instance to represent the PIP button
|
||||
@IBOutlet public weak var pipButton: VersaStatefulButton? = nil
|
||||
#endif
|
||||
|
||||
/// VersaStatefulButton instance to represent the rewind button
|
||||
@IBOutlet public weak var rewindButton: VersaStatefulButton? = nil
|
||||
@@ -52,7 +58,7 @@ open class VersaPlayerControls: UIView {
|
||||
@IBOutlet public weak var totalTimeLabel: VersaTimeLabel? = nil
|
||||
|
||||
/// UIView to be shown when buffering
|
||||
@IBOutlet public weak var bufferingView: UIView? = nil
|
||||
@IBOutlet public weak var bufferingView: View? = nil
|
||||
|
||||
private var wasPlayingBeforeRewinding: Bool = false
|
||||
private var wasPlayingBeforeForwarding: Bool = false
|
||||
@@ -62,19 +68,38 @@ open class VersaPlayerControls: UIView {
|
||||
public var skipSize: Double = 30
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self, name: VPlayer.VPlayerNotificationName.timeChanged.notification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: VPlayer.VPlayerNotificationName.play.notification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: VPlayer.VPlayerNotificationName.pause.notification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: VPlayer.VPlayerNotificationName.buffering.notification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: VPlayer.VPlayerNotificationName.endBuffering.notification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: VersaPlayer.VPlayerNotificationName.timeChanged.notification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: VersaPlayer.VPlayerNotificationName.play.notification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: VersaPlayer.VPlayerNotificationName.pause.notification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: VersaPlayer.VPlayerNotificationName.buffering.notification, object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: VersaPlayer.VPlayerNotificationName.endBuffering.notification, object: nil)
|
||||
}
|
||||
|
||||
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
#if os(macOS)
|
||||
|
||||
open override func touchesBegan(with event: NSEvent) {
|
||||
behaviour.hide()
|
||||
}
|
||||
|
||||
override open func didMoveToSuperview() {
|
||||
|
||||
override open func viewDidMoveToSuperview() {
|
||||
super.viewDidMoveToSuperview()
|
||||
layoutInSuperview()
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
behaviour.hide()
|
||||
}
|
||||
|
||||
open override func didMoveToSuperview() {
|
||||
super.didMoveToSuperview()
|
||||
layoutInSuperview()
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public func layoutInSuperview() {
|
||||
if let h = superview as? VersaPlayerControlsCoordinator {
|
||||
handler = h.player
|
||||
if behaviour == nil {
|
||||
@@ -88,38 +113,69 @@ open class VersaPlayerControls: UIView {
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - time: CMTime representation of the current playback time
|
||||
public func timeDidChange(toTime time: CMTime) {
|
||||
open func timeDidChange(toTime time: CMTime) {
|
||||
currentTimeLabel?.update(toTime: time.seconds)
|
||||
totalTimeLabel?.update(toTime: handler.player.endTime().seconds)
|
||||
seekbarSlider?.minimumValue = Float(handler.player.startTime().seconds)
|
||||
seekbarSlider?.maximumValue = Float(handler.player.endTime().seconds)
|
||||
seekbarSlider?.value = Float(time.seconds)
|
||||
setSeekbarSlider(start: handler.player.startTime().seconds, end: handler.player.endTime().seconds, at: time.seconds)
|
||||
|
||||
if !(handler.isSeeking || handler.isRewinding || handler.isForwarding) {
|
||||
behaviour.update(with: time.seconds)
|
||||
}
|
||||
}
|
||||
|
||||
public func setSeekbarSlider(start startValue: Double, end endValue: Double, at time: Double) {
|
||||
#if os(macOS)
|
||||
seekbarSlider?.minValue = startValue
|
||||
seekbarSlider?.maxValue = endValue
|
||||
seekbarSlider?.doubleValue = time
|
||||
#elseif os(iOS)
|
||||
seekbarSlider?.minimumValue = Float(startValue)
|
||||
seekbarSlider?.maximumValue = Float(endValue)
|
||||
seekbarSlider?.value = Float(time)
|
||||
#else
|
||||
seekbarSlider?.progress = Float(time) / Float(endValue)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Remove coordinator from player
|
||||
public func removeFromPlayer() {
|
||||
open func removeFromPlayer() {
|
||||
controlsCoordinator.removeFromSuperview()
|
||||
}
|
||||
|
||||
/// Prepare controls targets and notification listeners
|
||||
public func prepare() {
|
||||
layout()
|
||||
open func prepare() {
|
||||
stretchToEdges()
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
playPauseButton?.target = self
|
||||
playPauseButton?.action = #selector(togglePlayback(sender:))
|
||||
|
||||
fullscreenButton?.target = self
|
||||
fullscreenButton?.action = #selector(toggleFullscreen(sender:))
|
||||
|
||||
rewindButton?.target = self
|
||||
rewindButton?.action = #selector(rewindToggle(sender:))
|
||||
|
||||
forwardButton?.target = self
|
||||
forwardButton?.action = #selector(forwardToggle(sender:))
|
||||
|
||||
skipForwardButton?.target = self
|
||||
skipForwardButton?.action = #selector(skipForward(sender:))
|
||||
|
||||
skipBackwardButton?.target = self
|
||||
skipBackwardButton?.action = #selector(skipBackward(sender:))
|
||||
|
||||
prepareSeekbar()
|
||||
seekbarSlider?.target = self
|
||||
seekbarSlider?.action = #selector(playheadChanged(with:))
|
||||
|
||||
#else
|
||||
|
||||
playPauseButton?.addTarget(self, action: #selector(togglePlayback), for: .touchUpInside)
|
||||
|
||||
fullscreenButton?.addTarget(self, action: #selector(toggleFullscreen), for: .touchUpInside)
|
||||
|
||||
if !AVPictureInPictureController.isPictureInPictureSupported() {
|
||||
pipButton?.alpha = 0.3
|
||||
pipButton?.isUserInteractionEnabled = false
|
||||
}else {
|
||||
pipButton?.addTarget(self, action: #selector(togglePip), for: .touchUpInside)
|
||||
}
|
||||
|
||||
rewindButton?.addTarget(self, action: #selector(rewindToggle), for: .touchUpInside)
|
||||
|
||||
forwardButton?.addTarget(self, action: #selector(forwardToggle), for: .touchUpInside)
|
||||
@@ -128,16 +184,46 @@ open class VersaPlayerControls: UIView {
|
||||
skipBackwardButton?.addTarget(self, action: #selector(skipBackward), for: .touchUpInside)
|
||||
|
||||
prepareSeekbar()
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
if !AVPictureInPictureController.isPictureInPictureSupported() {
|
||||
pipButton?.alpha = 0.3
|
||||
pipButton?.isUserInteractionEnabled = false
|
||||
}else {
|
||||
pipButton?.addTarget(self, action: #selector(togglePip), for: .touchUpInside)
|
||||
}
|
||||
|
||||
seekbarSlider?.addTarget(self, action: #selector(playheadChanged(with:)), for: .valueChanged)
|
||||
seekbarSlider?.addTarget(self, action: #selector(seekingEnd), for: .touchUpInside)
|
||||
seekbarSlider?.addTarget(self, action: #selector(seekingEnd), for: .touchUpOutside)
|
||||
seekbarSlider?.addTarget(self, action: #selector(seekingStart), for: .touchDown)
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
prepareNotificationListener()
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
/// Layout in parent view
|
||||
public func layout() {
|
||||
open override func layout() {
|
||||
super.layout()
|
||||
stretchToEdges()
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
open override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
stretchToEdges()
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public func stretchToEdges() {
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
if let parent = superview {
|
||||
topAnchor.constraint(equalTo: parent.topAnchor).isActive = true
|
||||
@@ -148,60 +234,58 @@ open class VersaPlayerControls: UIView {
|
||||
}
|
||||
|
||||
/// Prepares the notification observers/listeners
|
||||
public func prepareNotificationListener() {
|
||||
NotificationCenter.default.addObserver(forName: VPlayer.VPlayerNotificationName.timeChanged.notification, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
if let time = notification.userInfo?[VPlayer.VPlayerNotificationInfoKey.time.rawValue] as? CMTime {
|
||||
open func prepareNotificationListener() {
|
||||
NotificationCenter.default.addObserver(forName: VersaPlayer.VPlayerNotificationName.timeChanged.notification, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
if let time = notification.userInfo?[VersaPlayer.VPlayerNotificationInfoKey.time.rawValue] as? CMTime {
|
||||
self.timeDidChange(toTime: time)
|
||||
}
|
||||
}
|
||||
NotificationCenter.default.addObserver(forName: VPlayer.VPlayerNotificationName.didEnd.notification, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
NotificationCenter.default.addObserver(forName: VersaPlayer.VPlayerNotificationName.didEnd.notification, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
self.playPauseButton?.set(active: false)
|
||||
}
|
||||
NotificationCenter.default.addObserver(forName: VPlayer.VPlayerNotificationName.play.notification, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
NotificationCenter.default.addObserver(forName: VersaPlayer.VPlayerNotificationName.play.notification, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
self.playPauseButton?.set(active: true)
|
||||
}
|
||||
NotificationCenter.default.addObserver(forName: VPlayer.VPlayerNotificationName.pause.notification, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
NotificationCenter.default.addObserver(forName: VersaPlayer.VPlayerNotificationName.pause.notification, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
self.playPauseButton?.set(active: false)
|
||||
}
|
||||
NotificationCenter.default.addObserver(forName: VPlayer.VPlayerNotificationName.endBuffering.notification, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
NotificationCenter.default.addObserver(forName: VersaPlayer.VPlayerNotificationName.endBuffering.notification, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
self.hideBuffering()
|
||||
}
|
||||
NotificationCenter.default.addObserver(forName: VPlayer.VPlayerNotificationName.buffering.notification, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
NotificationCenter.default.addObserver(forName: VersaPlayer.VPlayerNotificationName.buffering.notification, object: nil, queue: OperationQueue.main) { (notification) in
|
||||
self.showBuffering()
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare the seekbar values
|
||||
public func prepareSeekbar() {
|
||||
seekbarSlider?.value = Float(handler.player.currentTime().seconds)
|
||||
seekbarSlider?.minimumValue = Float(handler.player.startTime().seconds)
|
||||
seekbarSlider?.maximumValue = Float(handler.player.endTime().seconds)
|
||||
open func prepareSeekbar() {
|
||||
setSeekbarSlider(start: handler.player.startTime().seconds, end: handler.player.endTime().seconds, at: handler.player.currentTime().seconds)
|
||||
}
|
||||
|
||||
/// Show buffering view
|
||||
public func showBuffering() {
|
||||
open func showBuffering() {
|
||||
bufferingView?.isHidden = false
|
||||
}
|
||||
|
||||
/// Hide buffering view
|
||||
public func hideBuffering() {
|
||||
open func hideBuffering() {
|
||||
bufferingView?.isHidden = true
|
||||
}
|
||||
|
||||
/// Skip forward (n) seconds in time
|
||||
@IBAction public func skipForward() {
|
||||
@IBAction open func skipForward(sender: Any? = nil) {
|
||||
let time = handler.player.currentTime() + CMTime(seconds: skipSize, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
handler.player.seek(to: time)
|
||||
}
|
||||
|
||||
/// Skip backward (n) seconds in time
|
||||
@IBAction public func skipBackward() {
|
||||
@IBAction open func skipBackward(sender: Any? = nil) {
|
||||
let time = handler.player.currentTime() - CMTime(seconds: skipSize, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
handler.player.seek(to: time)
|
||||
}
|
||||
|
||||
/// End seeking
|
||||
@IBAction public func seekingEnd() {
|
||||
@IBAction open func seekingEnd(sender: Any? = nil) {
|
||||
handler.isSeeking = false
|
||||
if wasPlayingBeforeSeeking {
|
||||
handler.play()
|
||||
@@ -209,17 +293,33 @@ open class VersaPlayerControls: UIView {
|
||||
}
|
||||
|
||||
/// Start Seeking
|
||||
@IBAction public func seekingStart() {
|
||||
@IBAction open func seekingStart(sender: Any? = nil) {
|
||||
wasPlayingBeforeSeeking = handler.isPlaying
|
||||
handler.isSeeking = true
|
||||
handler.pause()
|
||||
}
|
||||
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
/// Playhead changed in NSSlider
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - sender: NSSlider that updated
|
||||
@IBAction open func playheadChanged(with sender: NSSlider) {
|
||||
let value = sender.doubleValue
|
||||
let time = CMTime(seconds: value, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
handler.player.seek(to: time)
|
||||
behaviour.update(with: time.seconds)
|
||||
}
|
||||
|
||||
#elseif os(iOS)
|
||||
|
||||
/// Playhead changed in UISlider
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - sender: UISlider that updated
|
||||
@IBAction public func playheadChanged(with sender: UISlider) {
|
||||
@IBAction open func playheadChanged(with sender: UISlider) {
|
||||
let value = Double(sender.value)
|
||||
let time = CMTime(seconds: value, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
||||
handler.player.seek(to: time)
|
||||
@@ -227,18 +327,20 @@ open class VersaPlayerControls: UIView {
|
||||
}
|
||||
|
||||
/// Toggle PIP mode
|
||||
@IBAction public func togglePip() {
|
||||
@IBAction open func togglePip() {
|
||||
handler.setNativePip(enabled: !handler.isPipModeEnabled)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// Toggle fullscreen mode
|
||||
@IBAction public func toggleFullscreen() {
|
||||
@IBAction open func toggleFullscreen(sender: Any? = nil) {
|
||||
fullscreenButton?.set(active: !handler.isFullscreenModeEnabled)
|
||||
handler.setFullscreen(enabled: !handler.isFullscreenModeEnabled)
|
||||
}
|
||||
|
||||
/// Toggle playback
|
||||
@IBAction public func togglePlayback() {
|
||||
@IBAction open func togglePlayback(sender: Any? = nil) {
|
||||
if handler.isRewinding || handler.isForwarding {
|
||||
handler.player.rate = 1
|
||||
playPauseButton?.set(active: true)
|
||||
@@ -248,7 +350,7 @@ open class VersaPlayerControls: UIView {
|
||||
playPauseButton?.set(active: false)
|
||||
handler.pause()
|
||||
}else {
|
||||
if handler.playbackDelegate?.playbackShouldBegin(forPlayer: handler.player) ?? true {
|
||||
if handler.playbackDelegate?.playbackShouldBegin(player: handler.player) ?? true {
|
||||
playPauseButton?.set(active: true)
|
||||
handler.play()
|
||||
}
|
||||
@@ -256,7 +358,7 @@ open class VersaPlayerControls: UIView {
|
||||
}
|
||||
|
||||
/// Toggle rewind
|
||||
@IBAction public func rewindToggle() {
|
||||
@IBAction open func rewindToggle(sender: Any? = nil) {
|
||||
if handler.player.currentItem?.canPlayFastReverse ?? false {
|
||||
if handler.isRewinding {
|
||||
rewindButton?.set(active: false)
|
||||
@@ -279,7 +381,7 @@ open class VersaPlayerControls: UIView {
|
||||
}
|
||||
|
||||
/// Forward toggle
|
||||
@IBAction public func forwardToggle() {
|
||||
@IBAction open func forwardToggle(sender: Any? = nil) {
|
||||
if handler.player.currentItem?.canPlayFastForward ?? false {
|
||||
if handler.isForwarding {
|
||||
forwardButton?.set(active: false)
|
||||
Generated
Regular → Executable
+69
-24
@@ -6,24 +6,53 @@
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
import CoreMedia
|
||||
import AVFoundation
|
||||
|
||||
open class VersaPlayerControlsCoordinator: UIView, VersaPlayerGestureRecieverViewDelegate {
|
||||
open class VersaPlayerControlsCoordinator: View, VersaPlayerGestureRecieverViewDelegate {
|
||||
|
||||
/// VersaPlayer instance being used
|
||||
var player: VersaPlayer!
|
||||
var player: VersaPlayerView!
|
||||
|
||||
/// VersaPlayerControls instance being used
|
||||
var controls: VersaPlayerControls!
|
||||
public var controls: VersaPlayerControls!
|
||||
|
||||
/// VersaPlayerGestureRecieverView instance being used
|
||||
var gestureReciever: VersaPlayerGestureRecieverView!
|
||||
public var gestureReciever: VersaPlayerGestureRecieverView!
|
||||
|
||||
override open func didMoveToSuperview() {
|
||||
#if os(macOS)
|
||||
|
||||
override open func viewDidMoveToSuperview() {
|
||||
super.viewDidMoveToSuperview()
|
||||
configureView()
|
||||
}
|
||||
|
||||
open override func layout() {
|
||||
super.layout()
|
||||
stretchToEdges()
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
open override func didMoveToSuperview() {
|
||||
super.didMoveToSuperview()
|
||||
if let h = superview as? VersaPlayer {
|
||||
configureView()
|
||||
}
|
||||
|
||||
open override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
stretchToEdges()
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public func configureView() {
|
||||
if let h = superview as? VersaPlayerView {
|
||||
player = h
|
||||
if controls != nil {
|
||||
addSubview(controls)
|
||||
@@ -31,14 +60,18 @@ open class VersaPlayerControlsCoordinator: UIView, VersaPlayerGestureRecieverVie
|
||||
if gestureReciever == nil {
|
||||
gestureReciever = VersaPlayerGestureRecieverView()
|
||||
gestureReciever.delegate = self
|
||||
#if os(macOS)
|
||||
addSubview(gestureReciever, positioned: NSWindow.OrderingMode.below, relativeTo: nil)
|
||||
#else
|
||||
addSubview(gestureReciever)
|
||||
sendSubview(toBack: gestureReciever)
|
||||
#endif
|
||||
}
|
||||
layout()
|
||||
stretchToEdges()
|
||||
}
|
||||
}
|
||||
|
||||
public func layout() {
|
||||
public func stretchToEdges() {
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
if let parent = superview {
|
||||
topAnchor.constraint(equalTo: parent.topAnchor).isActive = true
|
||||
@@ -52,11 +85,19 @@ open class VersaPlayerControlsCoordinator: UIView, VersaPlayerGestureRecieverVie
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - scale: CGFloat value
|
||||
public func didPinch(with scale: CGFloat) {
|
||||
if player.renderingView.renderingLayer.playerLayer.videoGravity == AVLayerVideoGravity.resizeAspect {
|
||||
player.renderingView.renderingLayer.playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||
open func didPinch(with scale: CGFloat) {
|
||||
|
||||
}
|
||||
|
||||
/// Notifies when tap was recognized
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - point: CGPoint at which tap was recognized
|
||||
open func didTap(at point: CGPoint) {
|
||||
if controls.behaviour.showingControls {
|
||||
controls.behaviour.hide()
|
||||
}else {
|
||||
player.renderingView.renderingLayer.playerLayer.videoGravity = AVLayerVideoGravity.resizeAspect
|
||||
controls.behaviour.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,11 +105,11 @@ open class VersaPlayerControlsCoordinator: UIView, VersaPlayerGestureRecieverVie
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - point: CGPoint at which tap was recognized
|
||||
public func didTap(at point: CGPoint) {
|
||||
if controls.behaviour.showingControls {
|
||||
controls.behaviour.hide()
|
||||
open func didDoubleTap(at point: CGPoint) {
|
||||
if player.renderingView.renderingLayer.playerLayer.videoGravity == AVLayerVideoGravity.resizeAspect {
|
||||
player.renderingView.renderingLayer.playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||
}else {
|
||||
controls.behaviour.show()
|
||||
player.renderingView.renderingLayer.playerLayer.videoGravity = AVLayerVideoGravity.resizeAspect
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,14 +118,18 @@ open class VersaPlayerControlsCoordinator: UIView, VersaPlayerGestureRecieverVie
|
||||
/// - Parameters:
|
||||
/// - translation: translation of pan in CGPoint representation
|
||||
/// - at: initial point recognized
|
||||
public func didPan(with translation: CGPoint, initially at: CGPoint) {
|
||||
let percentageTranslation: Double = Double(translation.x / gestureReciever.bounds.width)
|
||||
player.player.seek(to:
|
||||
CMTime.init(
|
||||
seconds: player.player.endTime().seconds * percentageTranslation,
|
||||
preferredTimescale: CMTimeScale(NSEC_PER_SEC)
|
||||
)
|
||||
)
|
||||
open func didPan(with translation: CGPoint, initially at: CGPoint) {
|
||||
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
/// Swipe was recognized
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - direction: gestureDirection
|
||||
open func didSwipe(with direction: UISwipeGestureRecognizer.Direction) {
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
Generated
Regular → Executable
+106
-31
@@ -1,35 +1,64 @@
|
||||
//
|
||||
// VersaPlayer.swift
|
||||
// VersaPlayer Demo
|
||||
// VersaPlayerView.swift
|
||||
// VersaPlayerView Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
import CoreMedia
|
||||
import AVFoundation
|
||||
import AVKit
|
||||
|
||||
open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
|
||||
|
||||
#if os(macOS)
|
||||
public typealias View = NSView
|
||||
#else
|
||||
public typealias View = UIView
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
public typealias PIPProtocol = AVPictureInPictureControllerDelegate
|
||||
#else
|
||||
public protocol PIPProtocol {}
|
||||
#endif
|
||||
|
||||
open class VersaPlayerView: View, PIPProtocol {
|
||||
|
||||
deinit {
|
||||
player.replaceCurrentItem(with: nil)
|
||||
}
|
||||
|
||||
/// VersaPlayer extension dictionary
|
||||
public var extensions: [String: VersaPlayerExtension] = [:]
|
||||
|
||||
/// AVPlayer used in VersaPlayer implementation
|
||||
public var player: VPlayer!
|
||||
public var player: VersaPlayer!
|
||||
|
||||
/// VPlayerRenderingView instance
|
||||
public var renderingView: VPlayerRenderingView!
|
||||
/// VersaPlayerControls instance being used to display controls
|
||||
public var controls: VersaPlayerControls? = nil
|
||||
|
||||
/// VersaPlayerRenderingView instance
|
||||
public var renderingView: VersaPlayerRenderingView!
|
||||
|
||||
/// VersaPlayerPlaybackDelegate instance
|
||||
public var playbackDelegate: VersaPlayerPlaybackDelegate? = nil
|
||||
|
||||
/// VersaPlayer initial container
|
||||
private var nonFullscreenContainer: UIView!
|
||||
/// VersaPlayerDecryptionDelegate instance to be used only when a VPlayer item with isEncrypted = true is passed
|
||||
public var decryptionDelegate: VersaPlayerDecryptionDelegate? = nil
|
||||
|
||||
/// VersaPlayer initial container
|
||||
private var nonFullscreenContainer: View!
|
||||
|
||||
#if os(iOS)
|
||||
/// AVPictureInPictureController instance
|
||||
public var pipController: AVPictureInPictureController? = nil
|
||||
#endif
|
||||
|
||||
/// Whether player is prepared
|
||||
public var ready: Bool = false
|
||||
@@ -49,6 +78,12 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
|
||||
/// Whether PIP Mode is enabled via pipController
|
||||
public var isPipModeEnabled: Bool = false
|
||||
|
||||
#if os(macOS)
|
||||
open override var wantsLayer: Bool {
|
||||
get { return true } set { }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Whether Player is Fast Forwarding
|
||||
public var isForwarding: Bool {
|
||||
return player.rate > 1
|
||||
@@ -69,12 +104,40 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
|
||||
prepare()
|
||||
}
|
||||
|
||||
/// VersaPlayerControls instance to display controls in player, using VersaPlayerGestureRecieverView instance
|
||||
/// to handle gestures
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - controls: VersaPlayerControls instance used to display controls
|
||||
/// - gestureReciever: Optional gesture reciever view to be used to recieve gestures
|
||||
public func use(controls: VersaPlayerControls, with gestureReciever: VersaPlayerGestureRecieverView? = nil) {
|
||||
let coordinator = VersaPlayerControlsCoordinator()
|
||||
coordinator.player = self
|
||||
coordinator.controls = controls
|
||||
coordinator.gestureReciever = gestureReciever
|
||||
controls.controlsCoordinator = coordinator
|
||||
#if os(macOS)
|
||||
addSubview(coordinator, positioned: NSWindow.OrderingMode.above, relativeTo: renderingView)
|
||||
#else
|
||||
addSubview(coordinator)
|
||||
bringSubview(toFront: coordinator)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Update controls to specified time
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - time: Time to be updated to
|
||||
public func updateControls(toTime time: CMTime) {
|
||||
controls?.timeDidChange(toTime: time)
|
||||
}
|
||||
|
||||
/// Add a VersaPlayerExtension instance to the current player
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - ext: The instance of the extension.
|
||||
/// - name: The name of the extension.
|
||||
public func addExtension(extension ext: VersaPlayerExtension, with name: String) {
|
||||
open func addExtension(extension ext: VersaPlayerExtension, with name: String) {
|
||||
ext.player = self
|
||||
ext.prepare()
|
||||
extensions[name] = ext
|
||||
@@ -84,17 +147,17 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - name: The name of the extension.
|
||||
public func getExtension(with name: String) -> VersaPlayerExtension? {
|
||||
open func getExtension(with name: String) -> VersaPlayerExtension? {
|
||||
return extensions[name]
|
||||
}
|
||||
|
||||
/// Prepares the player to play
|
||||
public func prepare() {
|
||||
open func prepare() {
|
||||
ready = true
|
||||
player = VPlayer()
|
||||
player = VersaPlayer()
|
||||
player.handler = self
|
||||
player.preparePlayerPlaybackDelegate()
|
||||
renderingView = VPlayerRenderingView(with: self)
|
||||
renderingView = VersaPlayerRenderingView(with: self)
|
||||
layout(view: renderingView, into: self)
|
||||
}
|
||||
|
||||
@@ -103,7 +166,10 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
|
||||
/// - Parameters:
|
||||
/// - view: The view to layout.
|
||||
/// - into: The container view.
|
||||
public func layout(view: UIView, into: UIView) {
|
||||
open func layout(view: View, into: View? = nil) {
|
||||
guard let into = into else {
|
||||
return
|
||||
}
|
||||
into.addSubview(view)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.topAnchor.constraint(equalTo: into.topAnchor).isActive = true
|
||||
@@ -112,38 +178,45 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
|
||||
view.bottomAnchor.constraint(equalTo: into.bottomAnchor).isActive = true
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
/// Enables or disables PIP when available (when device is supported)
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - enabled: Whether or not to enable
|
||||
public func setNativePip(enabled: Bool) {
|
||||
open func setNativePip(enabled: Bool) {
|
||||
if enabled {
|
||||
pipController?.startPictureInPicture()
|
||||
}else {
|
||||
pipController?.stopPictureInPicture()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Enables or disables fullscreen
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - enabled: Whether or not to enable
|
||||
public func setFullscreen(enabled: Bool) {
|
||||
open func setFullscreen(enabled: Bool) {
|
||||
if enabled == isFullscreenModeEnabled {
|
||||
return
|
||||
}
|
||||
if enabled {
|
||||
#if os(macOS)
|
||||
if let window = NSApplication.shared.keyWindow {
|
||||
nonFullscreenContainer = superview
|
||||
removeFromSuperview()
|
||||
layout(view: self, into: window.contentView)
|
||||
}
|
||||
#else
|
||||
if let window = UIApplication.shared.keyWindow {
|
||||
nonFullscreenContainer = superview
|
||||
removeFromSuperview()
|
||||
layout(view: self, into: window)
|
||||
let value = UIInterfaceOrientation.landscapeLeft.rawValue
|
||||
UIDevice.current.setValue(value, forKey: "orientation")
|
||||
UIViewController.attemptRotationToDeviceOrientation()
|
||||
}
|
||||
#endif
|
||||
}else {
|
||||
removeFromSuperview()
|
||||
layout(view: self, into: nonFullscreenContainer)
|
||||
let value = UIInterfaceOrientation.portrait.rawValue
|
||||
UIDevice.current.setValue(value, forKey: "orientation")
|
||||
UIViewController.attemptRotationToDeviceOrientation()
|
||||
}
|
||||
|
||||
isFullscreenModeEnabled = enabled
|
||||
@@ -153,7 +226,7 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: The VPlayerItem instance to add to player.
|
||||
public func set(item: VPlayerItem?) {
|
||||
open func set(item: VersaPlayerItem?) {
|
||||
if !ready {
|
||||
prepare()
|
||||
}
|
||||
@@ -165,21 +238,21 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
|
||||
}
|
||||
|
||||
/// Play
|
||||
@IBAction public func play() {
|
||||
if playbackDelegate?.playbackShouldBegin(forPlayer: player) ?? true {
|
||||
@IBAction open func play(sender: Any? = nil) {
|
||||
if playbackDelegate?.playbackShouldBegin(player: player) ?? true {
|
||||
player.play()
|
||||
isPlaying = true
|
||||
}
|
||||
}
|
||||
|
||||
/// Pause
|
||||
@IBAction public func pause() {
|
||||
@IBAction open func pause(sender: Any? = nil) {
|
||||
player.pause()
|
||||
isPlaying = false
|
||||
}
|
||||
|
||||
/// Toggle Playback
|
||||
@IBAction public func togglePlayback() {
|
||||
@IBAction open func togglePlayback(sender: Any? = nil) {
|
||||
if isPlaying {
|
||||
pause()
|
||||
}else {
|
||||
@@ -187,22 +260,24 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
public func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||
#if os(iOS)
|
||||
open func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||
//hide fallback
|
||||
}
|
||||
|
||||
public func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||
open func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||
//show fallback
|
||||
}
|
||||
|
||||
public func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||
open func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||
isPipModeEnabled = false
|
||||
controls?.controlsCoordinator.isHidden = false
|
||||
}
|
||||
|
||||
public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||
open func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||
controls?.controlsCoordinator.isHidden = true
|
||||
isPipModeEnabled = true
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
Generated
-20
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// VersaSeekbarSlider.swift
|
||||
// VersaPlayer Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
open class VersaSeekbarSlider: UISlider {
|
||||
|
||||
@IBInspectable public var thumbImage: UIImage? = nil {
|
||||
didSet {
|
||||
setThumbImage(thumbImage, for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Generated
-30
@@ -1,30 +0,0 @@
|
||||
//
|
||||
// VersaRewindButton.swift
|
||||
// VersaPlayer Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@IBDesignable
|
||||
open class VersaStatefulButton: UIButton {
|
||||
|
||||
@IBInspectable public var activeImage: UIImage? = nil
|
||||
@IBInspectable public var inactiveImage: UIImage? = nil {
|
||||
didSet {
|
||||
setImage(inactiveImage, for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
public func set(active: Bool) {
|
||||
if active {
|
||||
setImage(activeImage, for: .normal)
|
||||
}else {
|
||||
setImage(inactiveImage, for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
//
|
||||
// VersaPlayerGestureRecieverView.swift
|
||||
// VersaPlayer Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
open class VersaPlayerGestureRecieverView: UIView {
|
||||
|
||||
/// VersaPlayerGestureRecieverViewDelegate instance
|
||||
public var delegate: VersaPlayerGestureRecieverViewDelegate? = nil
|
||||
|
||||
/// UITapGestureRecognizer
|
||||
public var tapGesture: UITapGestureRecognizer? = nil
|
||||
|
||||
/// UIPanGestureRecognizer
|
||||
public var panGesture: UIPanGestureRecognizer? = nil
|
||||
|
||||
/// UIPinchGestureRecognizer
|
||||
public var pinchGesture: UIPinchGestureRecognizer? = nil
|
||||
|
||||
/// Whether or not reciever view is ready
|
||||
public var ready: Bool = false
|
||||
|
||||
/// Pan gesture initial point
|
||||
public var panGestureInitialPoint: CGPoint = CGPoint.zero
|
||||
|
||||
override open func didMoveToSuperview() {
|
||||
super.didMoveToSuperview()
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
if let parent = superview {
|
||||
topAnchor.constraint(equalTo: parent.topAnchor).isActive = true
|
||||
leftAnchor.constraint(equalTo: parent.leftAnchor).isActive = true
|
||||
rightAnchor.constraint(equalTo: parent.rightAnchor).isActive = true
|
||||
bottomAnchor.constraint(equalTo: parent.bottomAnchor).isActive = true
|
||||
}
|
||||
if !ready {
|
||||
prepare()
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare the view gesture recognizers
|
||||
public func prepare() {
|
||||
ready = true
|
||||
isUserInteractionEnabled = true
|
||||
tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapHandler(with:)))
|
||||
tapGesture?.numberOfTapsRequired = 1
|
||||
pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinchHandler(with:)))
|
||||
panGesture = UIPanGestureRecognizer(target: self, action: #selector(panHandler(with:)))
|
||||
panGesture?.minimumNumberOfTouches = 1
|
||||
|
||||
addGestureRecognizer(tapGesture!)
|
||||
addGestureRecognizer(panGesture!)
|
||||
addGestureRecognizer(pinchGesture!)
|
||||
}
|
||||
|
||||
|
||||
@objc public func tapHandler(with sender: UITapGestureRecognizer) {
|
||||
delegate?.didTap(at: sender.location(in: self))
|
||||
}
|
||||
|
||||
@objc public func pinchHandler(with sender: UIPinchGestureRecognizer) {
|
||||
if sender.state == .ended {
|
||||
delegate?.didPinch(with: sender.scale)
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func panHandler(with sender: UIPanGestureRecognizer) {
|
||||
if sender.state == .began {
|
||||
panGestureInitialPoint = sender.location(in: self)
|
||||
}
|
||||
delegate?.didPan(with: sender.translation(in: self), initially: panGestureInitialPoint)
|
||||
}
|
||||
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
//
|
||||
// VersaPlayerControlsExtension.swift
|
||||
// VersaPlayer Demo
|
||||
//
|
||||
// Created by Jose Quintero on 10/11/18.
|
||||
// Copyright © 2018 Quasar. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreMedia
|
||||
|
||||
public extension VersaPlayer {
|
||||
|
||||
private var versaPlayerControlsTag: Int { return 2000 }
|
||||
|
||||
/// VersaPlayerControls instance being used to display controls
|
||||
public var controls: VersaPlayerControls? {
|
||||
get {
|
||||
return viewWithTag(versaPlayerControlsTag) as? VersaPlayerControls
|
||||
}
|
||||
}
|
||||
|
||||
/// VersaPlayerControls instance to display controls in player, using VersaPlayerGestureRecieverView instance
|
||||
/// to handle gestures
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - controls: VersaPlayerControls instance used to display controls
|
||||
/// - gestureReciever: Optional gesture reciever view to be used to recieve gestures
|
||||
public func use(controls: VersaPlayerControls, with gestureReciever: VersaPlayerGestureRecieverView? = nil) {
|
||||
let coordinator = VersaPlayerControlsCoordinator()
|
||||
coordinator.player = self
|
||||
coordinator.controls = controls
|
||||
coordinator.gestureReciever = gestureReciever
|
||||
controls.controlsCoordinator = coordinator
|
||||
addSubview(coordinator)
|
||||
controls.tag = versaPlayerControlsTag
|
||||
bringSubview(toFront: controls)
|
||||
}
|
||||
|
||||
/// Update controls to specified time
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - time: Time to be updated to
|
||||
public func updateControls(toTime time: CMTime) {
|
||||
controls?.timeDidChange(toTime: time)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -283,7 +283,7 @@
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${SRCROOT}/Pods/Target Support Files/Pods-VersaPlayerAdsExtension_Example/Pods-VersaPlayerAdsExtension_Example-frameworks.sh",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-VersaPlayerAdsExtension_Example/Pods-VersaPlayerAdsExtension_Example-frameworks.sh",
|
||||
"${PODS_ROOT}/GoogleAds-IMA-iOS-SDK/GoogleInteractiveMediaAds.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/VersaPlayer/VersaPlayer.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/VersaPlayerAdsExtension/VersaPlayerAdsExtension.framework",
|
||||
@@ -296,7 +296,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-VersaPlayerAdsExtension_Example/Pods-VersaPlayerAdsExtension_Example-frameworks.sh\"\n";
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-VersaPlayerAdsExtension_Example/Pods-VersaPlayerAdsExtension_Example-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
BA24E07B0506C095E3F06889 /* [CP] Check Pods Manifest.lock */ = {
|
||||
@@ -323,7 +323,7 @@
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${SRCROOT}/Pods/Target Support Files/Pods-VersaPlayerAdsExtension_Tests/Pods-VersaPlayerAdsExtension_Tests-frameworks.sh",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-VersaPlayerAdsExtension_Tests/Pods-VersaPlayerAdsExtension_Tests-frameworks.sh",
|
||||
"${PODS_ROOT}/GoogleAds-IMA-iOS-SDK/GoogleInteractiveMediaAds.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/VersaPlayer/VersaPlayer.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework",
|
||||
@@ -336,7 +336,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-VersaPlayerAdsExtension_Tests/Pods-VersaPlayerAdsExtension_Tests-frameworks.sh\"\n";
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-VersaPlayerAdsExtension_Tests/Pods-VersaPlayerAdsExtension_Tests-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
@@ -12,7 +12,7 @@ import VersaPlayerAdsExtension
|
||||
|
||||
class ViewController: UIViewController, VersaPlayerAdManagerBehaviourDelegate {
|
||||
|
||||
@IBOutlet weak var player: VersaPlayer!
|
||||
@IBOutlet weak var player: VersaPlayerView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@@ -20,7 +20,7 @@ class ViewController: UIViewController, VersaPlayerAdManagerBehaviourDelegate {
|
||||
player.useAds(manager: VersaPlayerAdsManager.init(with: player, presentingIn: self))
|
||||
player.adsManager?.behaviour.delegate = self
|
||||
if let url = URL.init(string: "http://rmcdn.2mdn.net/Demo/html5/output.mp4") {
|
||||
let item = VPlayerItem(url: url)
|
||||
let item = VersaPlayerItem(url: url)
|
||||
player.set(item: item)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class ViewController: UIViewController, VersaPlayerAdManagerBehaviourDelegate {
|
||||
player.adsManager?.requestAds()
|
||||
}
|
||||
|
||||
func willShowAdsFor(player: VPlayer) {
|
||||
func willShowAdsFor(player: VersaPlayer) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'VersaPlayerAdsExtension'
|
||||
s.version = '0.1.2'
|
||||
s.version = '1.0.1'
|
||||
s.summary = 'VersaPlayer Extension to enable video ads.'
|
||||
s.description = 'VersaPlayer Extension to enable video ads functionality.'
|
||||
s.homepage = 'https://github.com/josejuanqm/VersaPlayerAdsExtension'
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ public class VersaPlayerAdManagerBehaviour {
|
||||
|
||||
}
|
||||
|
||||
public func willShowAdsFor(player: VPlayer) {
|
||||
public func willShowAdsFor(player: VersaPlayer) {
|
||||
self.handler.player.controls?.controlsCoordinator.isHidden = true
|
||||
delegate?.willShowAdsFor(player: player)
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import Foundation
|
||||
import UIKit
|
||||
import VersaPlayer
|
||||
|
||||
public extension VersaPlayer {
|
||||
public extension VersaPlayerView {
|
||||
|
||||
public var adsManager: VersaPlayerAdsManager? {
|
||||
let adsManager = getExtension(with: "adsManager") as? VersaPlayerAdsManager
|
||||
|
||||
+1
-1
@@ -10,5 +10,5 @@ import Foundation
|
||||
import VersaPlayer
|
||||
|
||||
public protocol VersaPlayerAdManagerBehaviourDelegate {
|
||||
func willShowAdsFor(player: VPlayer)
|
||||
func willShowAdsFor(player: VersaPlayer)
|
||||
}
|
||||
|
||||
+3
@@ -17,4 +17,7 @@ public protocol VersaPlayerAdsManagerDisplayDelegate {
|
||||
func ads(manager: IMAAdsManager!, didReceiveError error: IMAAdError!)
|
||||
func adsManagerDidRequestContentPause(_ adsManager: IMAAdsManager!)
|
||||
func adsManagerDidRequestContentResume(_ adsManager: IMAAdsManager!)
|
||||
func companionSlots(for manager: IMAAdsManager) -> [IMACompanionAdSlot]
|
||||
func renderingSettings(for manager: IMAAdsManager) -> IMAAdsRenderingSettings
|
||||
func shouldAutoPlayAds() -> Bool
|
||||
}
|
||||
|
||||
@@ -21,15 +21,14 @@ public class VersaPlayerAdsManager: VersaPlayerExtension, IMAAdsLoaderDelegate,
|
||||
public var displayDelegate: VersaPlayerAdsManagerDisplayDelegate?
|
||||
public var tag: String!
|
||||
public var showingAds: Bool = false
|
||||
public var preRollShown: Bool = false
|
||||
public var postRollShown: Bool = false
|
||||
public var secondsShown: [Double] = []
|
||||
public var adsRenderingSettings: IMAAdsRenderingSettings!
|
||||
|
||||
public init(with player: VersaPlayer, presentingIn controller: UIViewController) {
|
||||
public init(with player: VersaPlayerView, presentingIn controller: UIViewController, and delegate: VersaPlayerAdsManagerDisplayDelegate? = nil) {
|
||||
super.init(with: player)
|
||||
self.behaviour = VersaPlayerAdManagerBehaviour()
|
||||
self.behaviour.handler = self
|
||||
self.controller = controller
|
||||
self.displayDelegate = delegate
|
||||
setUpContentPlayer()
|
||||
setUpAdsLoader()
|
||||
player.addObserver(self, forKeyPath: "isPipModeEnabled", options: NSKeyValueObservingOptions.new, context: nil)
|
||||
@@ -44,12 +43,14 @@ public class VersaPlayerAdsManager: VersaPlayerExtension, IMAAdsLoaderDelegate,
|
||||
}
|
||||
|
||||
public func setUpAdsLoader() {
|
||||
adsLoader = IMAAdsLoader(settings: nil)
|
||||
let settings = IMASettings.init()
|
||||
settings.autoPlayAdBreaks = displayDelegate?.shouldAutoPlayAds() ?? true
|
||||
adsLoader = IMAAdsLoader(settings: settings)
|
||||
adsLoader!.delegate = self
|
||||
}
|
||||
|
||||
public func requestAds(using pip: Bool = false) {
|
||||
let adDisplayContainer = IMAAdDisplayContainer(adContainer: player.renderingView, companionSlots: nil)
|
||||
let adDisplayContainer = IMAAdDisplayContainer(adContainer: player.renderingView, companionSlots: self.adsManager == nil ? nil : displayDelegate?.companionSlots(for: self.adsManager!))
|
||||
var request: IMAAdsRequest
|
||||
if !pip {
|
||||
request = IMAAdsRequest(
|
||||
@@ -78,8 +79,7 @@ public class VersaPlayerAdsManager: VersaPlayerExtension, IMAAdsLoaderDelegate,
|
||||
displayDelegate?.ads(loader: loader, didLoad: adsLoadedData)
|
||||
adsManager = adsLoadedData.adsManager
|
||||
adsManager!.delegate = self
|
||||
let adsRenderingSettings = IMAAdsRenderingSettings()
|
||||
adsRenderingSettings.webOpenerPresentingController = nil
|
||||
adsRenderingSettings = displayDelegate?.renderingSettings(for: adsManager!)
|
||||
|
||||
adsManager!.initialize(with: adsRenderingSettings)
|
||||
}
|
||||
@@ -113,16 +113,18 @@ public class VersaPlayerAdsManager: VersaPlayerExtension, IMAAdsLoaderDelegate,
|
||||
}
|
||||
|
||||
public func adsManager(_ adsManager: IMAAdsManager!, didReceive event: IMAAdEvent!) {
|
||||
displayDelegate?.ads(manager: adsManager, didReceive: event)
|
||||
if (event.type == IMAAdEventType.LOADED) {
|
||||
showingAds = true
|
||||
behaviour.willShowAdsFor(player: player.player)
|
||||
adsManager.start()
|
||||
displayDelegate?.ads(manager: adsManager, didReceiveEvent: event)
|
||||
if displayDelegate?.shouldAutoPlayAds() ?? true {
|
||||
if (event.type == IMAAdEventType.LOADED) {
|
||||
showingAds = true
|
||||
behaviour.willShowAdsFor(player: player.player)
|
||||
adsManager.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func adsManager(_ adsManager: IMAAdsManager!, didReceive error: IMAAdError!) {
|
||||
displayDelegate?.ads(manager: adsManager, didReceive: error)
|
||||
displayDelegate?.ads(manager: adsManager, didReceiveError: error)
|
||||
player.play()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user