2 Commits

Author SHA1 Message Date
Jose Quintero 9ec0cfaa8e Support for lastest versa player version 2018-11-04 17:34:16 -06:00
Jose Quintero 1c78258f17 Bug fixing 2018-10-25 17:59:13 -06:00
52 changed files with 1678 additions and 1670 deletions
+2 -6
View File
@@ -3,10 +3,6 @@ use_frameworks!
target 'VersaPlayerOverlayContentExtension_Example' do
pod 'VersaPlayerOverlayContentExtension', :path => '../'
target 'VersaPlayerOverlayContentExtension_Tests' do
inherit! :search_paths
pod 'VersaPlayer'
pod 'FBSnapshotTestCase' , '~> 2.1.4'
end
pod 'VersaPlayer'
pod 'FBSnapshotTestCase' , '~> 2.1.4'
end
+5 -5
View File
@@ -4,8 +4,8 @@ PODS:
- FBSnapshotTestCase/Core (2.1.4)
- FBSnapshotTestCase/SwiftSupport (2.1.4):
- FBSnapshotTestCase/Core
- VersaPlayer (0.1.1)
- VersaPlayerOverlayContentExtension (0.1.4):
- VersaPlayer (1.2.1)
- VersaPlayerOverlayContentExtension (0.1.6):
- VersaPlayer
DEPENDENCIES:
@@ -24,9 +24,9 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a
VersaPlayer: 92f5eab6d9ce0a12bcfc5eca4618446f03931ec8
VersaPlayerOverlayContentExtension: d3cca8a799cf58369d4ac4637276a67a090f01b4
VersaPlayer: ae33859d21559ff631b0dfb159f212af70631ef2
VersaPlayerOverlayContentExtension: ddddd5e0da8aea0ba1b48511019b6122dbac8dea
PODFILE CHECKSUM: 26320abea2adf3d2ed79e3e641641149244c0896
PODFILE CHECKSUM: 7373f52ff6adc7369a4d55b4c114f3805aafefb7
COCOAPODS: 1.5.3
@@ -1,6 +1,6 @@
{
"name": "VersaPlayerOverlayContentExtension",
"version": "0.1.4",
"version": "0.1.6",
"summary": "VersaPlayer extension to enable overlay content.",
"description": "VersaPlayer extension to enable overlay content functionality.",
"homepage": "https://github.com/josejuanqm/VersaPlayerOverlayContentExtension",
@@ -13,7 +13,7 @@
},
"source": {
"git": "https://github.com/josejuanqm/VersaPlayerOverlayContentExtension.git",
"tag": "0.1.4"
"tag": "0.1.6"
},
"social_media_url": "https://twitter.com/josejuanqm",
"platforms": {
+5 -5
View File
@@ -4,8 +4,8 @@ PODS:
- FBSnapshotTestCase/Core (2.1.4)
- FBSnapshotTestCase/SwiftSupport (2.1.4):
- FBSnapshotTestCase/Core
- VersaPlayer (0.1.1)
- VersaPlayerOverlayContentExtension (0.1.4):
- VersaPlayer (1.2.1)
- VersaPlayerOverlayContentExtension (0.1.6):
- VersaPlayer
DEPENDENCIES:
@@ -24,9 +24,9 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a
VersaPlayer: 92f5eab6d9ce0a12bcfc5eca4618446f03931ec8
VersaPlayerOverlayContentExtension: d3cca8a799cf58369d4ac4637276a67a090f01b4
VersaPlayer: ae33859d21559ff631b0dfb159f212af70631ef2
VersaPlayerOverlayContentExtension: ddddd5e0da8aea0ba1b48511019b6122dbac8dea
PODFILE CHECKSUM: 26320abea2adf3d2ed79e3e641641149244c0896
PODFILE CHECKSUM: 7373f52ff6adc7369a4d55b4c114f3805aafefb7
COCOAPODS: 1.5.3
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,39 @@
# Acknowledgements
This application makes use of the following third party libraries:
## FBSnapshotTestCase
BSD License
For the FBSnapshotTestCase software
Copyright (c) 2013, Facebook, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## VersaPlayer
Copyright (c) 2018 jose.juan.qm@gmail.com <jose.quintero@fox.com>
@@ -12,6 +12,45 @@
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>BSD License
For the FBSnapshotTestCase software
Copyright (c) 2013, Facebook, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</string>
<key>License</key>
<string>BSD</string>
<key>Title</key>
<string>FBSnapshotTestCase</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2018 jose.juan.qm@gmail.com &lt;jose.quintero@fox.com&gt;
@@ -143,10 +143,12 @@ strip_invalid_archs() {
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework"
install_framework "${BUILT_PRODUCTS_DIR}/VersaPlayer/VersaPlayer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/VersaPlayerOverlayContentExtension/VersaPlayerOverlayContentExtension.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework"
install_framework "${BUILT_PRODUCTS_DIR}/VersaPlayer/VersaPlayer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/VersaPlayerOverlayContentExtension/VersaPlayerOverlayContentExtension.framework"
fi
@@ -1,9 +1,9 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerOverlayContentExtension"
FRAMEWORK_SEARCH_PATHS = $(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerOverlayContentExtension"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
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}/VersaPlayerOverlayContentExtension/VersaPlayerOverlayContentExtension.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "VersaPlayer" -framework "VersaPlayerOverlayContentExtension"
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}/VersaPlayerOverlayContentExtension/VersaPlayerOverlayContentExtension.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "FBSnapshotTestCase" -framework "VersaPlayer" -framework "VersaPlayerOverlayContentExtension"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
@@ -1,9 +1,9 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerOverlayContentExtension"
FRAMEWORK_SEARCH_PATHS = $(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks "${PODS_CONFIGURATION_BUILD_DIR}/FBSnapshotTestCase" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayer" "${PODS_CONFIGURATION_BUILD_DIR}/VersaPlayerOverlayContentExtension"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
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}/VersaPlayerOverlayContentExtension/VersaPlayerOverlayContentExtension.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "VersaPlayer" -framework "VersaPlayerOverlayContentExtension"
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}/VersaPlayerOverlayContentExtension/VersaPlayerOverlayContentExtension.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "FBSnapshotTestCase" -framework "VersaPlayer" -framework "VersaPlayerOverlayContentExtension"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
@@ -1,59 +0,0 @@
# Acknowledgements
This application makes use of the following third party libraries:
## VersaPlayer
Copyright (c) 2018 jose.juan.qm@gmail.com <jose.quintero@fox.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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## FBSnapshotTestCase
BSD License
For the FBSnapshotTestCase software
Copyright (c) 2013, Facebook, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Generated by CocoaPods - https://cocoapods.org
@@ -1,97 +0,0 @@
<?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>PreferenceSpecifiers</key>
<array>
<dict>
<key>FooterText</key>
<string>This application makes use of the following third party libraries:</string>
<key>Title</key>
<string>Acknowledgements</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2018 jose.juan.qm@gmail.com &lt;jose.quintero@fox.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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>VersaPlayer</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>BSD License
For the FBSnapshotTestCase software
Copyright (c) 2013, Facebook, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</string>
<key>License</key>
<string>BSD</string>
<key>Title</key>
<string>FBSnapshotTestCase</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Generated by CocoaPods - https://cocoapods.org</string>
<key>Title</key>
<string></string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
</array>
<key>StringsTable</key>
<string>Acknowledgements</string>
<key>Title</key>
<string>Acknowledgements</string>
</dict>
</plist>
@@ -1,5 +0,0 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_Pods_VersaPlayerOverlayContentExtension_Tests : NSObject
@end
@implementation PodsDummy_Pods_VersaPlayerOverlayContentExtension_Tests
@end
@@ -1,155 +0,0 @@
#!/bin/sh
set -e
set -u
set -o pipefail
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
fi
echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}"
SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
# Used as a return value for each invocation of `strip_invalid_archs` function.
STRIP_BINARY_RETVAL=0
# This protects against multiple targets copying the same framework dependency at the same time. The solution
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
# Copies and strips a vendored framework
install_framework()
{
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
local source="${BUILT_PRODUCTS_DIR}/$1"
elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
elif [ -r "$1" ]; then
local source="$1"
fi
local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
if [ -L "${source}" ]; then
echo "Symlinked..."
source="$(readlink "${source}")"
fi
# Use filter instead of exclude so missing patterns don't throw errors.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
local basename
basename="$(basename -s .framework "$1")"
binary="${destination}/${basename}.framework/${basename}"
if ! [ -r "$binary" ]; then
binary="${destination}/${basename}"
fi
# Strip invalid architectures so "fat" simulator / device frameworks work on device
if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
strip_invalid_archs "$binary"
fi
# Resign the code if required by the build settings to avoid unstable apps
code_sign_if_enabled "${destination}/$(basename "$1")"
# 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]})
for lib in $swift_runtime_libs; do
echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
code_sign_if_enabled "${destination}/${lib}"
done
fi
}
# Copies and strips a vendored dSYM
install_dsym() {
local source="$1"
if [ -r "$source" ]; then
# Copy the dSYM into a the targets temp dir.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
local basename
basename="$(basename -s .framework.dSYM "$source")"
binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
# Strip invalid architectures so "fat" simulator / device frameworks work on device
if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then
strip_invalid_archs "$binary"
fi
if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
# Move the stripped file into its final destination.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
else
# The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
fi
fi
}
# 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
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'"
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
code_sign_cmd="$code_sign_cmd &"
fi
echo "$code_sign_cmd"
eval "$code_sign_cmd"
fi
}
# Strip invalid architectures
strip_invalid_archs() {
binary="$1"
# Get architectures for current target binary
binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
# Intersect them with the architectures we are building for
intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
# If there are no archs supported by this binary then warn the user
if [[ -z "$intersected_archs" ]]; then
echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
STRIP_BINARY_RETVAL=0
return
fi
stripped=""
for arch in $binary_archs; do
if ! [[ "${ARCHS}" == *"$arch"* ]]; then
# Strip non-valid architectures in-place
lipo -remove "$arch" -output "$binary" "$binary" || exit 1
stripped="$stripped $arch"
fi
done
if [[ "$stripped" ]]; then
echo "Stripped $binary of architectures:$stripped"
fi
STRIP_BINARY_RETVAL=1
}
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/VersaPlayer/VersaPlayer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/VersaPlayer/VersaPlayer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework"
fi
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
wait
fi
@@ -1,118 +0,0 @@
#!/bin/sh
set -e
set -u
set -o pipefail
if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then
# If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy
# resources to, so exit 0 (signalling the script phase was successful).
exit 0
fi
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
> "$RESOURCES_TO_COPY"
XCASSET_FILES=()
# This protects against multiple targets copying the same framework dependency at the same time. The solution
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
case "${TARGETED_DEVICE_FAMILY:-}" in
1,2)
TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
;;
1)
TARGET_DEVICE_ARGS="--target-device iphone"
;;
2)
TARGET_DEVICE_ARGS="--target-device ipad"
;;
3)
TARGET_DEVICE_ARGS="--target-device tv"
;;
4)
TARGET_DEVICE_ARGS="--target-device watch"
;;
*)
TARGET_DEVICE_ARGS="--target-device mac"
;;
esac
install_resource()
{
if [[ "$1" = /* ]] ; then
RESOURCE_PATH="$1"
else
RESOURCE_PATH="${PODS_ROOT}/$1"
fi
if [[ ! -e "$RESOURCE_PATH" ]] ; then
cat << EOM
error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
EOM
exit 1
fi
case $RESOURCE_PATH in
*.storyboard)
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
;;
*.xib)
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
;;
*.framework)
echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
;;
*.xcdatamodel)
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
;;
*.xcdatamodeld)
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
;;
*.xcmappingmodel)
echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
;;
*.xcassets)
ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
;;
*)
echo "$RESOURCE_PATH" || true
echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
;;
esac
}
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
fi
rm -f "$RESOURCES_TO_COPY"
if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ]
then
# Find all other xcassets (this unfortunately includes those of path pods and other targets).
OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
while read line; do
if [[ $line != "${PODS_ROOT}*" ]]; then
XCASSET_FILES+=("$line")
fi
done <<<"$OTHER_XCASSETS"
if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
else
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist"
fi
fi
@@ -1,16 +0,0 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
FOUNDATION_EXPORT double Pods_VersaPlayerOverlayContentExtension_TestsVersionNumber;
FOUNDATION_EXPORT const unsigned char Pods_VersaPlayerOverlayContentExtension_TestsVersionString[];
@@ -1,11 +0,0 @@
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}/VersaPlayerOverlayContentExtension"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
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}/VersaPlayerOverlayContentExtension/VersaPlayerOverlayContentExtension.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "FBSnapshotTestCase" -framework "VersaPlayer"
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}/.
PODS_ROOT = ${SRCROOT}/Pods
@@ -1,6 +0,0 @@
framework module Pods_VersaPlayerOverlayContentExtension_Tests {
umbrella header "Pods-VersaPlayerOverlayContentExtension_Tests-umbrella.h"
export *
module * { export * }
}
@@ -1,11 +0,0 @@
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}/VersaPlayerOverlayContentExtension"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
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}/VersaPlayerOverlayContentExtension/VersaPlayerOverlayContentExtension.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "FBSnapshotTestCase" -framework "VersaPlayer"
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}/.
PODS_ROOT = ${SRCROOT}/Pods
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.1.1</string>
<string>1.2.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.1.4</string>
<string>0.1.6</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+236 -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>
## :warning: tvOS Information
If you are looking for the tvOS player, head over to https://github.com/josejuanqm/TVersaPlayer
## 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,21 +117,151 @@ 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
@@ -59,6 +271,20 @@ Full documentation avilable https://josejuanqm.github.io/Libraries-Documentation
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.
@@ -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
}
@@ -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,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 AVPlayerStatus.readyToPlay:
handler.playbackDelegate?.playbackReady(player: self)
case AVPlayerStatus.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,9 +11,9 @@ import Foundation
open class VersaPlayerExtension: NSObject {
/// VersaPlayer instance being used
open var player: VersaPlayer
open var player: VersaPlayerView
public init(with player: VersaPlayer) {
public init(with player: VersaPlayerView) {
self.player = player
}
@@ -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,13 @@ 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)
let controller = AVPictureInPictureController(playerLayer: playerLayer)
controller?.delegate = player
player.pipController = controller
addSublayer(playerLayer)
}
@@ -9,19 +9,19 @@
import UIKit
import AVKit
open class VPlayerRenderingView: UIView {
open class VersaPlayerRenderingView: UIView {
/// 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
}
@@ -33,7 +33,7 @@ open class VPlayerRenderingView: UIView {
override open func layoutSubviews() {
super.layoutSubviews()
if renderingLayer == nil {
renderingLayer = VPlayerLayer.init(with: player)
renderingLayer = VersaPlayerLayer.init(with: player)
layer.addSublayer(renderingLayer.playerLayer)
}
@@ -18,7 +18,7 @@ open class VersaStatefulButton: UIButton {
}
}
public func set(active: Bool) {
open func set(active: Bool) {
if active {
setImage(activeImage, for: .normal)
}else {
@@ -12,7 +12,7 @@ open class VersaTimeLabel: UILabel {
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)
@@ -50,7 +50,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,7 +61,7 @@ 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
@@ -69,7 +69,7 @@ open class VersaPlayerControlsBehaviour {
}
/// Default deactivation block
public func defaultDeactivationBlock() {
open func defaultDeactivationBlock() {
UIView.animate(withDuration: 0.3, animations: {
self.controls.alpha = 0
}, completion: {
@@ -80,7 +80,7 @@ open class VersaPlayerControlsBehaviour {
}
/// Hide the controls
public func hide() {
open func hide() {
if deactivationBlock != nil {
deactivationBlock!(controls)
}else {
@@ -90,7 +90,7 @@ open class VersaPlayerControlsBehaviour {
}
/// Show the controls
public func show() {
open func show() {
if !shouldShowControls {
return
}
@@ -1,6 +1,6 @@
//
// VersaPlayerGestureRecieverView.swift
// VersaPlayer Demo
// VersaPlayerView Demo
//
// Created by Jose Quintero on 10/11/18.
// Copyright © 2018 Quasar. All rights reserved.
@@ -13,9 +13,12 @@ open class VersaPlayerGestureRecieverView: UIView {
/// VersaPlayerGestureRecieverViewDelegate instance
public var delegate: VersaPlayerGestureRecieverViewDelegate? = nil
/// UITapGestureRecognizer
/// Single tap UITapGestureRecognizer
public var tapGesture: UITapGestureRecognizer? = nil
/// Double tap UITapGestureRecognizer
public var doubleTapGesture: UITapGestureRecognizer? = nil
/// UIPanGestureRecognizer
public var panGesture: UIPanGestureRecognizer? = nil
@@ -43,32 +46,43 @@ open class VersaPlayerGestureRecieverView: UIView {
}
/// Prepare the view gesture recognizers
public func prepare() {
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 public func tapHandler(with sender: UITapGestureRecognizer) {
@objc open func tapHandler(with sender: UITapGestureRecognizer) {
delegate?.didTap(at: sender.location(in: self))
}
@objc public func pinchHandler(with sender: UIPinchGestureRecognizer) {
@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 public func panHandler(with sender: UIPanGestureRecognizer) {
@objc open func panHandler(with sender: UIPanGestureRecognizer) {
if sender.state == .began {
panGestureInitialPoint = sender.location(in: self)
}
@@ -1,6 +1,6 @@
//
// VersaPlayerGestureRecieverViewDelegate.swift
// VersaPlayer Demo
// VersaPlayerView Demo
//
// Created by Jose Quintero on 10/11/18.
// Copyright © 2018 Quasar. All rights reserved.
@@ -20,9 +20,15 @@ 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:
@@ -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)
}
}
@@ -13,7 +13,7 @@ import AVKit
open class VersaPlayerControls: UIView {
/// VersaPlayer intance being controlled
public var handler: VersaPlayer!
public var handler: VersaPlayerView!
/// VersaPlayerControlsBehaviour being used to validate ui
public var behaviour: VersaPlayerControlsBehaviour!
@@ -62,11 +62,11 @@ 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?) {
@@ -88,7 +88,7 @@ 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)
@@ -101,12 +101,12 @@ open class VersaPlayerControls: UIView {
}
/// Remove coordinator from player
public func removeFromPlayer() {
open func removeFromPlayer() {
controlsCoordinator.removeFromSuperview()
}
/// Prepare controls targets and notification listeners
public func prepare() {
open func prepare() {
layout()
playPauseButton?.addTarget(self, action: #selector(togglePlayback), for: .touchUpInside)
@@ -137,7 +137,7 @@ open class VersaPlayerControls: UIView {
}
/// Layout in parent view
public func layout() {
open func layout() {
translatesAutoresizingMaskIntoConstraints = false
if let parent = superview {
topAnchor.constraint(equalTo: parent.topAnchor).isActive = true
@@ -148,60 +148,60 @@ 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() {
open func prepareSeekbar() {
seekbarSlider?.value = Float(handler.player.currentTime().seconds)
seekbarSlider?.minimumValue = Float(handler.player.startTime().seconds)
seekbarSlider?.maximumValue = Float(handler.player.endTime().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() {
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() {
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() {
handler.isSeeking = false
if wasPlayingBeforeSeeking {
handler.play()
@@ -209,7 +209,7 @@ open class VersaPlayerControls: UIView {
}
/// Start Seeking
@IBAction public func seekingStart() {
@IBAction open func seekingStart() {
wasPlayingBeforeSeeking = handler.isPlaying
handler.isSeeking = true
handler.pause()
@@ -219,7 +219,7 @@ open class VersaPlayerControls: UIView {
///
/// - 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 +227,18 @@ open class VersaPlayerControls: UIView {
}
/// Toggle PIP mode
@IBAction public func togglePip() {
@IBAction open func togglePip() {
handler.setNativePip(enabled: !handler.isPipModeEnabled)
}
/// Toggle fullscreen mode
@IBAction public func toggleFullscreen() {
@IBAction open func toggleFullscreen() {
fullscreenButton?.set(active: !handler.isFullscreenModeEnabled)
handler.setFullscreen(enabled: !handler.isFullscreenModeEnabled)
}
/// Toggle playback
@IBAction public func togglePlayback() {
@IBAction open func togglePlayback() {
if handler.isRewinding || handler.isForwarding {
handler.player.rate = 1
playPauseButton?.set(active: true)
@@ -248,7 +248,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 +256,7 @@ open class VersaPlayerControls: UIView {
}
/// Toggle rewind
@IBAction public func rewindToggle() {
@IBAction open func rewindToggle() {
if handler.player.currentItem?.canPlayFastReverse ?? false {
if handler.isRewinding {
rewindButton?.set(active: false)
@@ -279,7 +279,7 @@ open class VersaPlayerControls: UIView {
}
/// Forward toggle
@IBAction public func forwardToggle() {
@IBAction open func forwardToggle() {
if handler.player.currentItem?.canPlayFastForward ?? false {
if handler.isForwarding {
forwardButton?.set(active: false)
@@ -13,17 +13,17 @@ import AVFoundation
open class VersaPlayerControlsCoordinator: UIView, 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() {
super.didMoveToSuperview()
if let h = superview as? VersaPlayer {
if let h = superview as? VersaPlayerView {
player = h
if controls != nil {
addSubview(controls)
@@ -38,7 +38,7 @@ open class VersaPlayerControlsCoordinator: UIView, VersaPlayerGestureRecieverVie
}
}
public func layout() {
open func layout() {
translatesAutoresizingMaskIntoConstraints = false
if let parent = superview {
topAnchor.constraint(equalTo: parent.topAnchor).isActive = true
@@ -52,11 +52,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 +72,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,7 +85,7 @@ 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) {
open func didPan(with translation: CGPoint, initially at: CGPoint) {
let percentageTranslation: Double = Double(translation.x / gestureReciever.bounds.width)
player.player.seek(to:
CMTime.init(
@@ -1,6 +1,6 @@
//
// VersaPlayer.swift
// VersaPlayer Demo
// VersaPlayerView.swift
// VersaPlayerView Demo
//
// Created by Jose Quintero on 10/11/18.
// Copyright © 2018 Quasar. All rights reserved.
@@ -11,20 +11,26 @@ import CoreMedia
import AVFoundation
import AVKit
open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
open class VersaPlayerView: UIView, AVPictureInPictureControllerDelegate {
/// VersaPlayer extension dictionary
public var extensions: [String: VersaPlayerExtension] = [:]
/// AVPlayer used in VersaPlayer implementation
public var player: VPlayer!
public var player: VersaPlayer!
/// VersaPlayerControls instance being used to display controls
public var controls: VersaPlayerControls? = nil
/// VPlayerRenderingView instance
public var renderingView: VPlayerRenderingView!
public var renderingView: VersaPlayerRenderingView!
/// VersaPlayerPlaybackDelegate instance
public var playbackDelegate: VersaPlayerPlaybackDelegate? = nil
/// 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: UIView!
@@ -74,7 +80,7 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
/// - 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 +90,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 +109,7 @@ 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: UIView, into: UIView) {
into.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.topAnchor.constraint(equalTo: into.topAnchor).isActive = true
@@ -116,7 +122,7 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
///
/// - Parameters:
/// - enabled: Whether or not to enable
public func setNativePip(enabled: Bool) {
open func setNativePip(enabled: Bool) {
if enabled {
pipController?.startPictureInPicture()
}else {
@@ -128,22 +134,19 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
///
/// - Parameters:
/// - enabled: Whether or not to enable
public func setFullscreen(enabled: Bool) {
open func setFullscreen(enabled: Bool) {
if enabled == isFullscreenModeEnabled {
return
}
if enabled {
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()
}
}else {
removeFromSuperview()
layout(view: self, into: nonFullscreenContainer)
let value = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
UIViewController.attemptRotationToDeviceOrientation()
}
isFullscreenModeEnabled = enabled
@@ -153,7 +156,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()
}
@@ -164,22 +167,48 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
}
}
/// 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)
bringSubview(toFront: controls)
self.controls = controls
}
/// Update controls to specified time
///
/// - Parameters:
/// - time: Time to be updated to
public func updateControls(toTime time: CMTime) {
controls?.timeDidChange(toTime: time)
}
/// Play
@IBAction public func play() {
if playbackDelegate?.playbackShouldBegin(forPlayer: player) ?? true {
@IBAction open func play() {
if playbackDelegate?.playbackShouldBegin(player: player) ?? true {
player.play()
isPlaying = true
}
}
/// Pause
@IBAction public func pause() {
@IBAction open func pause() {
player.pause()
isPlaying = false
}
/// Toggle Playback
@IBAction public func togglePlayback() {
@IBAction open func togglePlayback() {
if isPlaying {
pause()
}else {
@@ -187,20 +216,20 @@ open class VersaPlayer: UIView, AVPictureInPictureControllerDelegate {
}
}
public func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
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
}
@@ -12,13 +12,11 @@
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
CDBBEBBB3284BA03C1AF1F36 /* Pods_VersaPlayerOverlayContentExtension_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 422C3B66293A75759E15CB2C /* Pods_VersaPlayerOverlayContentExtension_Example.framework */; };
AF579BC80FE04C7BA0483B09 /* Pods_VersaPlayerOverlayContentExtension_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8AD8344BF34E94201CFCAB8 /* Pods_VersaPlayerOverlayContentExtension_Example.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
19BE7522841DB1F31244CEDB /* Pods-VersaPlayerOverlayContentExtension_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VersaPlayerOverlayContentExtension_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-VersaPlayerOverlayContentExtension_Tests/Pods-VersaPlayerOverlayContentExtension_Tests.debug.xcconfig"; sourceTree = "<group>"; };
3B3EA70D344F87BE2B6F7342 /* VersaPlayerOverlayContentExtension.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = VersaPlayerOverlayContentExtension.podspec; path = ../VersaPlayerOverlayContentExtension.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
422C3B66293A75759E15CB2C /* Pods_VersaPlayerOverlayContentExtension_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_VersaPlayerOverlayContentExtension_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
607FACD01AFB9204008FA782 /* VersaPlayerOverlayContentExtension_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VersaPlayerOverlayContentExtension_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -26,12 +24,11 @@
607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
8AB68C5D5DC86028B26EE610 /* Pods-VersaPlayerOverlayContentExtension_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VersaPlayerOverlayContentExtension_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-VersaPlayerOverlayContentExtension_Tests/Pods-VersaPlayerOverlayContentExtension_Tests.release.xcconfig"; sourceTree = "<group>"; };
8DF3ADA7A282FBE752C02BA3 /* Pods-VersaPlayerOverlayContentExtension_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VersaPlayerOverlayContentExtension_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-VersaPlayerOverlayContentExtension_Example/Pods-VersaPlayerOverlayContentExtension_Example.release.xcconfig"; sourceTree = "<group>"; };
A47852AA004508EC7061BF9F /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
CBC7206BE453D3298C595B8E /* Pods_VersaPlayerOverlayContentExtension_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_VersaPlayerOverlayContentExtension_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D54DE5A68FCD4E2CC17A5AF5 /* Pods-VersaPlayerOverlayContentExtension_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VersaPlayerOverlayContentExtension_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-VersaPlayerOverlayContentExtension_Example/Pods-VersaPlayerOverlayContentExtension_Example.debug.xcconfig"; sourceTree = "<group>"; };
A78291A316916BFCD146B57E /* Pods-VersaPlayerOverlayContentExtension_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VersaPlayerOverlayContentExtension_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-VersaPlayerOverlayContentExtension_Example/Pods-VersaPlayerOverlayContentExtension_Example.debug.xcconfig"; sourceTree = "<group>"; };
C8AD8344BF34E94201CFCAB8 /* Pods_VersaPlayerOverlayContentExtension_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_VersaPlayerOverlayContentExtension_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E81456EB946137D4E22D9878 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
FC67324BDD318674F712789A /* Pods-VersaPlayerOverlayContentExtension_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VersaPlayerOverlayContentExtension_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-VersaPlayerOverlayContentExtension_Example/Pods-VersaPlayerOverlayContentExtension_Example.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -39,41 +36,21 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CDBBEBBB3284BA03C1AF1F36 /* Pods_VersaPlayerOverlayContentExtension_Example.framework in Frameworks */,
AF579BC80FE04C7BA0483B09 /* Pods_VersaPlayerOverlayContentExtension_Example.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
18BB4E7223638CB36E042CA5 /* Pods */ = {
isa = PBXGroup;
children = (
D54DE5A68FCD4E2CC17A5AF5 /* Pods-VersaPlayerOverlayContentExtension_Example.debug.xcconfig */,
FC67324BDD318674F712789A /* Pods-VersaPlayerOverlayContentExtension_Example.release.xcconfig */,
19BE7522841DB1F31244CEDB /* Pods-VersaPlayerOverlayContentExtension_Tests.debug.xcconfig */,
8AB68C5D5DC86028B26EE610 /* Pods-VersaPlayerOverlayContentExtension_Tests.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
418B1EAF417DFF9C011FD6F9 /* Frameworks */ = {
isa = PBXGroup;
children = (
422C3B66293A75759E15CB2C /* Pods_VersaPlayerOverlayContentExtension_Example.framework */,
CBC7206BE453D3298C595B8E /* Pods_VersaPlayerOverlayContentExtension_Tests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
607FACC71AFB9204008FA782 = {
isa = PBXGroup;
children = (
607FACF51AFB993E008FA782 /* Podspec Metadata */,
607FACD21AFB9204008FA782 /* Example for VersaPlayerOverlayContentExtension */,
607FACD11AFB9204008FA782 /* Products */,
18BB4E7223638CB36E042CA5 /* Pods */,
418B1EAF417DFF9C011FD6F9 /* Frameworks */,
AB9BE6C9C6CDAB1AC7982BB5 /* Pods */,
873570A6E894640F9971E155 /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -117,6 +94,23 @@
name = "Podspec Metadata";
sourceTree = "<group>";
};
873570A6E894640F9971E155 /* Frameworks */ = {
isa = PBXGroup;
children = (
C8AD8344BF34E94201CFCAB8 /* Pods_VersaPlayerOverlayContentExtension_Example.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
AB9BE6C9C6CDAB1AC7982BB5 /* Pods */ = {
isa = PBXGroup;
children = (
A78291A316916BFCD146B57E /* Pods-VersaPlayerOverlayContentExtension_Example.debug.xcconfig */,
8DF3ADA7A282FBE752C02BA3 /* Pods-VersaPlayerOverlayContentExtension_Example.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -124,11 +118,11 @@
isa = PBXNativeTarget;
buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "VersaPlayerOverlayContentExtension_Example" */;
buildPhases = (
B4833FEAB1898CDA522A7AF5 /* [CP] Check Pods Manifest.lock */,
8EF19E4A669871A5C1CF906F /* [CP] Check Pods Manifest.lock */,
607FACCC1AFB9204008FA782 /* Sources */,
607FACCD1AFB9204008FA782 /* Frameworks */,
607FACCE1AFB9204008FA782 /* Resources */,
027689DE2F87B89C090B497A /* [CP] Embed Pods Frameworks */,
0D2C85268D21D00E38E1D8A7 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -187,18 +181,24 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
027689DE2F87B89C090B497A /* [CP] Embed Pods Frameworks */ = {
0D2C85268D21D00E38E1D8A7 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-VersaPlayerOverlayContentExtension_Example/Pods-VersaPlayerOverlayContentExtension_Example-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/FBSnapshotTestCase/FBSnapshotTestCase.framework",
"${BUILT_PRODUCTS_DIR}/VersaPlayer/VersaPlayer.framework",
"${BUILT_PRODUCTS_DIR}/VersaPlayerOverlayContentExtension/VersaPlayerOverlayContentExtension.framework",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
);
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSnapshotTestCase.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VersaPlayer.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VersaPlayerOverlayContentExtension.framework",
);
@@ -207,16 +207,20 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-VersaPlayerOverlayContentExtension_Example/Pods-VersaPlayerOverlayContentExtension_Example-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
B4833FEAB1898CDA522A7AF5 /* [CP] Check Pods Manifest.lock */ = {
8EF19E4A669871A5C1CF906F /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-VersaPlayerOverlayContentExtension_Example-checkManifestLockResult.txt",
);
@@ -360,7 +364,7 @@
};
607FACF01AFB9204008FA782 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D54DE5A68FCD4E2CC17A5AF5 /* Pods-VersaPlayerOverlayContentExtension_Example.debug.xcconfig */;
baseConfigurationReference = A78291A316916BFCD146B57E /* Pods-VersaPlayerOverlayContentExtension_Example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = VersaPlayerOverlayContentExtension/Info.plist;
@@ -375,7 +379,7 @@
};
607FACF11AFB9204008FA782 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FC67324BDD318674F712789A /* Pods-VersaPlayerOverlayContentExtension_Example.release.xcconfig */;
baseConfigurationReference = 8DF3ADA7A282FBE752C02BA3 /* Pods-VersaPlayerOverlayContentExtension_Example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = VersaPlayerOverlayContentExtension/Info.plist;
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@@ -21,7 +21,7 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bMZ-HI-RVE" customClass="VersaPlayer" customModule="VersaPlayer">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bMZ-HI-RVE" customClass="VersaPlayerView" customModule="VersaPlayer">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" cocoaTouchSystemColor="viewFlipsideBackgroundColor"/>
</view>
@@ -12,14 +12,14 @@ import VersaPlayerOverlayContentExtension
class ViewController: UIViewController {
@IBOutlet weak var player: VersaPlayer!
@IBOutlet weak var player: VersaPlayerView!
@IBOutlet weak var pauseOverlay: VersaPlayerOverlayContent!
override func viewDidLoad() {
super.viewDidLoad()
player.useOverlayContent(manager: VersaPlayerOverlayContentManager.init(with: player, and: 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)
}
}
@@ -36,7 +36,7 @@ extension ViewController: VersaPlayerOverlayContentManagerDelegate {
content.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
}
func shouldShowOverlayContentForPlayer(player: VersaPlayer, status: VersaPlayerOverlayContentManagerPlayerStatus) -> Bool {
func shouldShowOverlayContentForPlayer(player: VersaPlayerView, status: VersaPlayerOverlayContentManagerPlayerStatus) -> Bool {
switch status {
case .assetLoaded:
return true
@@ -51,16 +51,16 @@ extension ViewController: VersaPlayerOverlayContentManagerDelegate {
}
}
func viewForOverlayContentIn(player: VersaPlayer, status: VersaPlayerOverlayContentManagerPlayerStatus) -> VersaPlayerOverlayContent {
func viewForOverlayContentIn(player: VersaPlayerView, status: VersaPlayerOverlayContentManagerPlayerStatus) -> VersaPlayerOverlayContent {
pauseOverlay.shouldPausePlayerOnShow = true
return pauseOverlay
}
func willDisplayOverlayContentIn(player: VersaPlayer, content: VersaPlayerOverlayContent, status: VersaPlayerOverlayContentManagerPlayerStatus) {
func willDisplayOverlayContentIn(player: VersaPlayerView, content: VersaPlayerOverlayContent, status: VersaPlayerOverlayContentManagerPlayerStatus) {
}
func willRemoveOverlayContentIn(player: VersaPlayer, content: VersaPlayerOverlayContent, status: VersaPlayerOverlayContentManagerPlayerStatus) {
func willRemoveOverlayContentIn(player: VersaPlayerView, content: VersaPlayerOverlayContent, status: VersaPlayerOverlayContentManagerPlayerStatus) {
player.play()
}
+1 -1
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'VersaPlayerOverlayContentExtension'
s.version = '0.1.3'
s.version = '0.2.0'
s.summary = 'VersaPlayer extension to enable overlay content.'
s.description = 'VersaPlayer extension to enable overlay content functionality.'
s.homepage = 'https://github.com/josejuanqm/VersaPlayerOverlayContentExtension'
@@ -10,7 +10,7 @@ import Foundation
import UIKit
import VersaPlayer
public extension VersaPlayer {
public extension VersaPlayerView {
public var overlayContentManager: VersaPlayerOverlayContentManager? {
let overlayContentManager = getExtension(with: "overlayContentManager") as? VersaPlayerOverlayContentManager
@@ -12,8 +12,8 @@ import VersaPlayer
public protocol VersaPlayerOverlayContentManagerDelegate {
func positionIn(container: UIView, forContent content: VersaPlayerOverlayContent)
func shouldShowOverlayContentForPlayer(player: VersaPlayer, status: VersaPlayerOverlayContentManagerPlayerStatus) -> Bool
func viewForOverlayContentIn(player: VersaPlayer, status: VersaPlayerOverlayContentManagerPlayerStatus) -> VersaPlayerOverlayContent
func willDisplayOverlayContentIn(player: VersaPlayer, content: VersaPlayerOverlayContent, status: VersaPlayerOverlayContentManagerPlayerStatus)
func willRemoveOverlayContentIn(player: VersaPlayer, content: VersaPlayerOverlayContent, status: VersaPlayerOverlayContentManagerPlayerStatus)
func shouldShowOverlayContentForPlayer(player: VersaPlayerView, status: VersaPlayerOverlayContentManagerPlayerStatus) -> Bool
func viewForOverlayContentIn(player: VersaPlayerView, status: VersaPlayerOverlayContentManagerPlayerStatus) -> VersaPlayerOverlayContent
func willDisplayOverlayContentIn(player: VersaPlayerView, content: VersaPlayerOverlayContent, status: VersaPlayerOverlayContentManagerPlayerStatus)
func willRemoveOverlayContentIn(player: VersaPlayerView, content: VersaPlayerOverlayContent, status: VersaPlayerOverlayContentManagerPlayerStatus)
}
@@ -11,7 +11,7 @@ import VersaPlayer
open class VersaPlayerOverlayContent: UIView {
public var player: VersaPlayer!
public var player: VersaPlayerView!
public var shown: Bool = false
public var shouldPausePlayerOnShow: Bool = true
@@ -24,19 +24,21 @@ open class VersaPlayerOverlayContent: UIView {
public func show(with status: VersaPlayerOverlayContentManagerPlayerStatus) {
if shown { return } else { shown = true }
if shouldPausePlayerOnShow { player.pause() }
if shouldPausePlayerOnShow && player.isPlaying { player.pause() }
player.overlayContentManager?.delegate?.willDisplayOverlayContentIn(player: player, content: self, status: status)
player.controls?.behaviour.hide()
player.showOverlay(content: self)
}
public func hide(with status: VersaPlayerOverlayContentManagerPlayerStatus) {
player.overlayContentManager?.isShowing = false
player.overlayContentManager?.delegate?.willRemoveOverlayContentIn(player: player, content: self, status: status)
player.controls?.behaviour.show()
player.hideOverlay(content: self)
}
@IBAction public func hide() {
player.overlayContentManager?.isShowing = false
player.overlayContentManager?.delegate?.willRemoveOverlayContentIn(player: player, content: self, status: player.overlayContentManager?.status ?? .none)
player.controls?.behaviour.show()
player.hideOverlay(content: self)
@@ -16,42 +16,47 @@ open class VersaPlayerOverlayContentManager: VersaPlayerExtension {
public var delegate: VersaPlayerOverlayContentManagerDelegate? = nil
public var status: VersaPlayerOverlayContentManagerPlayerStatus? = nil
internal var isShowing: Bool = false
public init(with player: VersaPlayer, and delegate: VersaPlayerOverlayContentManagerDelegate) {
public init(with player: VersaPlayerView, and delegate: VersaPlayerOverlayContentManagerDelegate) {
super.init(with: player)
self.delegate = delegate
}
open override func prepare() {
NotificationCenter.default.addObserver(forName: VPlayer.VPlayerNotificationName.assetLoaded.notification, object: nil, queue: OperationQueue.main) { (notification) in
NotificationCenter.default.addObserver(forName: VersaPlayer.VPlayerNotificationName.assetLoaded.notification, object: nil, queue: OperationQueue.main) { (notification) in
self.status = VersaPlayerOverlayContentManagerPlayerStatus.assetLoaded
self.showIfNeededWith(status: self.status!)
}
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 {
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.status = VersaPlayerOverlayContentManagerPlayerStatus.timeChanged(round(time.seconds))
self.showIfNeededWith(status: self.status!)
}
}
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.status = VersaPlayerOverlayContentManagerPlayerStatus.didEnd
self.showIfNeededWith(status: self.status!)
}
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.status = VersaPlayerOverlayContentManagerPlayerStatus.didPlay
self.showIfNeededWith(status: self.status!)
}
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.status = VersaPlayerOverlayContentManagerPlayerStatus.didPause
self.showIfNeededWith(status: self.status!)
}
}
public func showIfNeededWith(status: VersaPlayerOverlayContentManagerPlayerStatus) {
if isShowing {
return
}
if (delegate?.shouldShowOverlayContentForPlayer(player: player, status: status) ?? false) {
if let view = delegate?.viewForOverlayContentIn(player: player, status: status) {
view.player = player
delegate?.willDisplayOverlayContentIn(player: player, content: view, status: status)
isShowing = true
view.show(with: status)
}
}