3 Commits

Author SHA1 Message Date
Jose Quintero 263b97a4a7 podspec update 2018-11-08 17:51:12 -06:00
Jose Quintero fa8f1b6ada Updated for VersaPlayer 2.1.3 2018-11-08 17:50:07 -06:00
Jose Quintero 4541449a45 Support for lastest VersaPlayer version 2018-11-04 17:47:42 -06:00
59 changed files with 2600 additions and 1259 deletions
+5 -5
View File
@@ -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": {
+5 -5
View File
@@ -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
File diff suppressed because it is too large Load Diff
@@ -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>
@@ -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}
@@ -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
@@ -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>
@@ -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
@@ -25,7 +25,7 @@
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2018 jose.juan.qm@gmail.com &lt;jose.quintero@fox.com&gt;
<string>Copyright (c) 2018 jose.juan.qm@gmail.com &lt;jose.juan.qm@gmail.com&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -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
@@ -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}/.
@@ -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}/.
@@ -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>
@@ -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
@@ -25,7 +25,7 @@
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2018 jose.juan.qm@gmail.com &lt;jose.quintero@fox.com&gt;
<string>Copyright (c) 2018 jose.juan.qm@gmail.com &lt;jose.juan.qm@gmail.com&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -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
@@ -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,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
View File
@@ -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>
@@ -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>
@@ -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}
+1 -1
View File
@@ -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
+238 -10
View File
@@ -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!
[![paypal](https://github.com/josejuanqm/VersaPlayer/blob/master/RepoAssets/Artboard.png)](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 {
}
@@ -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
}
@@ -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
}
}
@@ -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)
}
}
@@ -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
}
}
@@ -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)
}
}
@@ -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
}
@@ -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() {
}
}
@@ -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)
}
@@ -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)
}
@@ -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
}
@@ -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 {
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
@@ -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
@@ -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
}
@@ -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)
@@ -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
}
@@ -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
}
@@ -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)
}
}
}
@@ -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)
}
}
}
@@ -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)
}
}
@@ -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) {
}
+1 -1
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'VersaPlayerAdsExtension'
s.version = '0.4.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'
@@ -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)
}
@@ -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
@@ -10,5 +10,5 @@ import Foundation
import VersaPlayer
public protocol VersaPlayerAdManagerBehaviourDelegate {
func willShowAdsFor(player: VPlayer)
func willShowAdsFor(player: VersaPlayer)
}
@@ -23,7 +23,7 @@ public class VersaPlayerAdsManager: VersaPlayerExtension, IMAAdsLoaderDelegate,
public var showingAds: Bool = false
public var adsRenderingSettings: IMAAdsRenderingSettings!
public init(with player: VersaPlayer, presentingIn controller: UIViewController, and delegate: VersaPlayerAdsManagerDisplayDelegate? = nil) {
public init(with player: VersaPlayerView, presentingIn controller: UIViewController, and delegate: VersaPlayerAdsManagerDisplayDelegate? = nil) {
super.init(with: player)
self.behaviour = VersaPlayerAdManagerBehaviour()
self.behaviour.handler = self