MM-18490 Migrate app builds to circleCi (#3373)

* Add pods to source control

* Update Fastlane to work with circleCi

* Configure circleCi workflows to build the app

* Set BRANCH_TO_BUILD env var to CIRCLE_BRANCH for PRs

* Use different context for each platform and type

* Fix regex

* Add i18n check
This commit is contained in:
Elias Nahum 2019-10-21 23:42:49 +03:00 committed by GitHub
parent 4f1211a586
commit 010c099654
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
78 changed files with 6755 additions and 248 deletions

View file

@ -1,5 +1,201 @@
version: 2.1
executors:
android:
parameters:
resource_class:
default: large
type: string
environment:
NODE_OPTIONS: --max_old_space_size=12000
NODE_ENV: production
BABEL_ENV: production
docker:
- image: circleci/android:api-27-node
working_directory: ~/mattermost-mobile
resource_class: <<parameters.resource_class>>
ios:
environment:
NODE_OPTIONS: --max_old_space_size=12000
NODE_ENV: production
BABEL_ENV: production
macos:
xcode: "11.0.0"
working_directory: ~/mattermost-mobile
shell: /bin/bash --login -o pipefail
commands:
checkout-private:
description: "Checkout the private repo with build env vars"
steps:
- add_ssh_keys:
fingerprints:
- "59:4d:99:5e:1c:6d:30:36:6d:60:76:88:ff:a7:ab:63"
- run:
name: Clone the mobile private repo
command: git clone git@github.com:mattermost/mattermost-mobile-private.git ~/mattermost-mobile-private
android-gem-dependencies:
description: "Get Fastlane dependencies for Android builds"
steps:
- restore_cache:
name: Restore Fastlane cache
key: v1-gems-android-{{ checksum "fastlane/Gemfile.lock" }}-{{ arch }}
- run:
working_directory: fastlane
name: Download Fastlane dependencies
command: bundle install --path vendor/bundle
- save_cache:
name: Save Fastlane cache
key: v1-gems-android-{{ checksum "fastlane/Gemfile.lock" }}-{{ arch }}
paths:
- fastlane/vendor/bundle
android-gradle-dependencies:
description: "Get Gradle dependencies for Android buils"
steps:
- restore_cache:
name: Restore Gradle cache
key: v1-gradle-{{ checksum "android/build.gradle" }}-{{ checksum "android/app/build.gradle" }}
- run:
working_directory: android
name: Download Gradle dependencies
command: ./gradlew dependencies
- save_cache:
name: Save Gradle cache
paths:
- ~/.gradle
key: v1-gradle-{{ checksum "android/build.gradle" }}-{{ checksum "android/app/build.gradle" }}
assets:
description: "Generate app assets"
steps:
- restore_cache:
name: Restore assets cache
key: v1-assets-{{ checksum "assets/base/config.json" }}-{{ arch }}
- run:
name: Generate assets
command: make dist/assets
- save_cache:
name: Save assets cache
key: v1-assets-{{ checksum "assets/base/config.json" }}-{{ arch }}
paths:
- dist
ios-gem-dependencies:
description: "Get Fastlane dependencies for iOS builds"
steps:
- run:
name: Set Ruby version
command: echo "ruby-2.6.3" > ~/.ruby-version
- restore_cache:
name: Restore Fastlane cache
key: v1-gems-ios-{{ checksum "fastlane/Gemfile.lock" }}-{{ arch }}
- run:
working_directory: fastlane
name: Download Fastlane dependencies
command: bundle install --path vendor/bundle
- save_cache:
name: Save Fastlane cache
key: v1-gems-ios-{{ checksum "fastlane/Gemfile.lock" }}-{{ arch }}
paths:
- fastlane/vendor/bundle
npm-dependencies:
description: "Get JavaScript dependencies for the job"
steps:
- restore_cache:
name: Restore npm cache
key: v1-npm-{{ checksum "package.json" }}-{{ arch }}
- run:
name: Getting JavaScript dependencies
command: NODE_ENV=development npm install
- save_cache:
name: Save npm cache
key: v1-npm-{{ checksum "package.json" }}-{{ arch }}
paths:
- node_modules
build-android:
description: "Build the android app"
steps:
- checkout:
path: ~/mattermost-mobile
- checkout-private
- npm-dependencies
- assets
- android-gem-dependencies
- android-gradle-dependencies
- run:
name: Append Keystore to build Android
command: |
cp ~/mattermost-mobile-private/android/${STORE_FILE} android/app/${STORE_FILE}
echo MATTERMOST_RELEASE_STORE_FILE=${STORE_FILE} | tee -a android/gradle.properties > /dev/null
echo ${STORE_ALIAS} | tee -a android/gradle.properties > /dev/null
echo ${STORE_PASSWORD} | tee -a android/gradle.properties > /dev/null
- run:
working_directory: fastlane
name: Run fastlane to build android
no_output_timeout: 30m
command: bundle exec fastlane android build || exit 1
build-ios:
description: "Build the iOS app"
steps:
- checkout:
path: ~/mattermost-mobile
- npm-dependencies
- assets
- ios-gem-dependencies
- run:
working_directory: fastlane
name: Run fastlane to build iOS
no_output_timeout: 30m
command: bundle exec fastlane ios build || exit 1
deploy-android:
description: "Deploy apk to Google Play"
parameters:
apk_path:
type: string
steps:
- attach_workspace:
at: ~/mattermost-mobile
- deploy:
name: "Deploy apk to Google Play"
working_directory: fastlane
command: bundle exec fastlane android deploy apk:../<<parameters.apk_path>>
deploy-ios:
description: "Deploy ipa to TestFlight"
parameters:
ipa_path:
type: string
steps:
- attach_workspace:
at: ~/mattermost-mobile
- deploy:
name: "Deploy ipa to TestFlight"
working_directory: fastlane
command: bundle exec fastlane ios deploy ipa:../<<parameters.ipa_path>>
persist:
description: "Persist mattermost-mobile directory"
steps:
- persist_to_workspace:
root: ./
paths:
- ./
save:
description: "Save binaries artifacts"
parameters:
filename:
type: string
steps:
- store_artifacts:
path: ~/mattermost-mobile/<<parameters.filename>>
jobs:
test:
@ -7,17 +203,201 @@ jobs:
docker:
- image: circleci/node:10
steps:
- checkout
- run: |
echo assets/base/config.json
cat assets/base/config.json
# Avoid installing pods
touch .podinstall
# Run tests
make test || exit 1
- checkout:
path: ~/mattermost-mobile
- npm-dependencies
- assets
- run:
name: Check styles
command: npm run check
- run:
name: Running Tests
command: npm test
- run:
name: Check i18n
command: make i18n-extract-ci
build-android-beta:
executor: android
steps:
- build-android
- persist
- save:
filename: "Mattermost_Beta.apk"
build-android-release:
executor: android
steps:
- build-android
- persist
- save:
filename: "Mattermost.apk"
build-android-pr:
executor: android
environment:
BRANCH_TO_BUILD: ${CIRCLE_BRANCH}
steps:
- build-android
- persist
- save:
filename: "Mattermost_Beta.apk"
build-ios-beta:
executor: ios
steps:
- build-ios
- persist
- save:
filename: "Mattermost_Beta.ipa"
build-ios-release:
executor: ios
steps:
- build-ios
- persist
- save:
filename: "Mattermost.ipa"
build-ios-pr:
executor: ios
environment:
BRANCH_TO_BUILD: ${CIRCLE_BRANCH}
steps:
- build-ios
- persist
- save:
filename: "Mattermost_Beta.ipa"
deploy-android-release:
executor:
name: android
resource_class: medium
steps:
- deploy-android:
apk_path: Mattermost.apk
deploy-android-beta:
executor:
name: android
resource_class: medium
steps:
- deploy-android:
apk_path: Mattermost_Beta.apk
deploy-ios-release:
executor: ios
steps:
- deploy-ios:
ipa_path: Mattermost.ipa
deploy-ios-beta:
executor: ios
steps:
- deploy-ios:
ipa_path: Mattermost_Beta.ipa
workflows:
version: 2
pr-test:
build:
jobs:
- test
- build-android-release:
context: mattermost-mobile-android-release
requires:
- test
filters:
branches:
only:
- /^build-\d+$/
- /^build-android-\d+$/
- /^build-android-release-\d+$/
- deploy-android-release:
context: mattermost-mobile-android-release
requires:
- build-android-release
filters:
branches:
only:
- /^build-\d+$/
- /^build-android-\d+$/
- /^build-android-release-\d+$/
- build-android-beta:
context: mattermost-mobile-android-beta
requires:
- test
filters:
branches:
only:
- /^build-\d+$/
- /^build-android-\d+$/
- /^build-android-beta-\d+$/
- deploy-android-beta:
context: mattermost-mobile-android-beta
requires:
- build-android-beta
filters:
branches:
only:
- /^build-\d+$/
- /^build-android-\d+$/
- /^build-android-beta-\d+$/
- build-ios-release:
context: mattermost-mobile-ios-release
requires:
- test
filters:
branches:
only:
- /^build-\d+$/
- /^build-ios-\d+$/
- /^build-ios-release-\d+$/
- deploy-ios-release:
context: mattermost-mobile-ios-release
requires:
- build-ios-release
filters:
branches:
only:
- /^build-\d+$/
- /^build-ios-\d+$/
- /^build-ios-release-\d+$/
- build-ios-beta:
context: mattermost-mobile-ios-beta
requires:
- test
filters:
branches:
only:
- /^build-\d+$/
- /^build-ios-\d+$/
- /^build-ios-beta-\d+$/
- deploy-ios-beta:
context: mattermost-mobile-ios-beta
requires:
- build-ios-beta
filters:
branches:
only:
- /^build-\d+$/
- /^build-ios-\d+$/
- /^build-ios-beta-\d+$/
- build-android-pr:
context: mattermost-mobile-android-pr
requires:
- test
filters:
branches:
only: /^build-pr-.*/
- build-ios-pr:
context: mattermost-mobile-ios-pr
requires:
- test
filters:
branches:
only: /^build-pr-.*/

4
.gitignore vendored
View file

@ -85,10 +85,6 @@ ios/sentry.properties
.nyc_output
coverage
# Pods
.podinstall
ios/Pods/
# Bundle artifact
*.jsbundle

View file

@ -31,18 +31,6 @@ npm-ci: package.json
@echo Getting Javascript dependencies
@npm ci
.podinstall:
ifeq ($(OS), Darwin)
ifdef POD
@echo Getting Cocoapods dependencies;
@cd ios && pod install;
else
@echo "Cocoapods is not installed https://cocoapods.org/"
@exit 1
endif
endif
@touch $@
dist/assets: $(BASE_ASSETS) $(OVERRIDE_ASSETS)
@mkdir -p dist
@ -53,9 +41,9 @@ dist/assets: $(BASE_ASSETS) $(OVERRIDE_ASSETS)
@echo "Generating app assets"
@node scripts/make-dist-assets.js
pre-run: | node_modules .podinstall dist/assets ## Installs dependencies and assets
pre-run: | node_modules dist/assets ## Installs dependencies and assets
pre-build: | npm-ci .podinstall dist/assets ## Install dependencies and assets before building
pre-build: | npm-ci dist/assets ## Install dependencies and assets before building
check-style: node_modules ## Runs eslint
@echo Checking for style guide compliance
@ -65,10 +53,8 @@ clean: ## Cleans dependencies, previous builds and temp files
@echo Cleaning started
@rm -rf node_modules
@rm -f .podinstall
@rm -rf dist
@rm -rf ios/build
@rm -rf ios/Pods
@rm -rf android/app/build
@echo Cleanup finished

View file

@ -11,69 +11,69 @@ configured = false
is_build_pr = false
# Executes before anything else use to setup the script
before_all do |lane, options|
if lane.to_s == 'build_pr'
pr = options[:pr]
UI.success("Building #{pr}")
ENV['BRANCH_TO_BUILD'] = pr
is_build_pr = true
end
before_all do
is_build_pr = ENV['BUILD_PR'] == 'true'
UI.success("Building for release #{ENV['BUILD_FOR_RELEASE']}")
# Raises an error is git is not clean
if ENV['COMMIT_CHANGES_TO_GIT'] == 'true'
ensure_git_status_clean
end
if ENV['CIRCLECI'] != 'true'
# Raises an error is git is not clean
if ENV['COMMIT_CHANGES_TO_GIT'] == 'true'
ensure_git_status_clean
end
# Block to ensure we are on the right branch
branch = ENV['BRANCH_TO_BUILD'] || ENV['GIT_BRANCH']
begin
ensure_git_branch(
branch: branch
)
rescue
sh "git checkout #{branch}"
UI.success("Using branch \"#{branch}\"")
end
# Block to ensure we are on the right branch
branch = ENV['BRANCH_TO_BUILD'] || ENV['GIT_BRANCH']
begin
ensure_git_branch(
branch: branch
)
rescue
sh "git checkout #{branch}"
UI.success("Using branch \"#{branch}\"")
end
# If we are going to commit changes to git create a separate branch
# so no changes are done in the branch that is being built
if ENV['COMMIT_CHANGES_TO_GIT'] == 'true'
local_branch = ENV['GIT_LOCAL_BRANCH'] || 'build'
sh "git checkout -b #{local_branch}"
UI.success("Creating branch \"#{local_branch}\"")
# If we are going to commit changes to git create a separate branch
# so no changes are done in the branch that is being built
if ENV['COMMIT_CHANGES_TO_GIT'] == 'true'
local_branch = ENV['GIT_LOCAL_BRANCH'] || 'build'
sh "git checkout -b #{local_branch}"
UI.success("Creating branch \"#{local_branch}\"")
end
end
end
after_all do |lane|
if lane.to_s == 'build'
submit_to_store
end
if ENV['RESET_GIT_BRANCH'] == 'true'
branch = ENV['BRANCH_TO_BUILD'] || 'master'
package_id = ENV['MAIN_APP_IDENTIFIER'] || 'com.mattermost.rnbeta'
beta_dir = '../android/app/src/main/java/com/mattermost/rnbeta'
release_dir = "../android/app/src/main/java/#{package_id.gsub '.', '/'}"
if beta_dir != release_dir
sh "rm -rf #{release_dir}"
if ENV['CIRCLECI'] != 'true'
if lane.to_s == 'build'
submit_to_store
end
reset_git_repo(
force: true,
skip_clean: true
)
sh "git checkout #{branch}"
if ENV['RESET_GIT_BRANCH'] == 'true'
branch = ENV['BRANCH_TO_BUILD'] || 'master'
package_id = ENV['MAIN_APP_IDENTIFIER'] || 'com.mattermost.rnbeta'
beta_dir = '../android/app/src/main/java/com/mattermost/rnbeta'
release_dir = "../android/app/src/main/java/#{package_id.gsub '.', '/'}"
if ENV['COMMIT_CHANGES_TO_GIT'] == 'true'
local_branch = ENV['GIT_LOCAL_BRANCH'] || 'build'
sh "git branch -D #{local_branch}"
UI.success("Deleted working branch \"#{local_branch}\"")
if lane.to_s == 'build_pr'
sh 'git checkout master'
## Remove the branch for the PR
sh "git branch -D #{branch}"
UI.success("Deleted PR branch \"#{branch}\"")
if beta_dir != release_dir
sh "rm -rf #{release_dir}"
end
reset_git_repo(
force: true,
skip_clean: true
)
sh "git checkout #{branch}"
if ENV['COMMIT_CHANGES_TO_GIT'] == 'true'
local_branch = ENV['GIT_LOCAL_BRANCH'] || 'build'
sh "git branch -D #{local_branch}"
UI.success("Deleted working branch \"#{local_branch}\"")
if lane.to_s == 'build_pr'
sh 'git checkout master'
## Remove the branch for the PR
sh "git branch -D #{branch}"
UI.success("Deleted PR branch \"#{branch}\"")
end
end
end
end
@ -196,20 +196,110 @@ lane :unsigned do
unsigned
end
desc 'Build the app using a PR so QA can test'
lane :build_pr do
configure
desc 'Upload file to s3'
lane :upload_file_to_s3 do |options|
os_type = options[:os_type]
file = options[:file]
file_plist = ""
# Build the android app
self.runner.current_platform = :android
build
if file.nil? || file.empty?
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
filename = app_name.gsub(" ", "_")
# Build the ios app
self.runner.current_platform = :ios
build
if os_type == 'Android'
file = "#{filename}.apk"
elsif os_type == 'iOS'
file = "#{filename}.ipa"
file_plist = "#{filename}.plist"
end
end
build_folder_path = Dir[File.expand_path('..')].first
file_path = "#{build_folder_path}/#{file}"
unless ENV['AWS_BUCKET_NAME'].nil? || ENV['AWS_BUCKET_NAME'].empty? || ENV['AWS_REGION'].nil? || ENV['AWS_REGION'].empty? || file_path.nil?
s3_region = ENV['AWS_REGION']
s3_bucket = ENV['AWS_BUCKET_NAME']
s3_folder = ''
version_number = ''
build_number = ''
if is_build_pr
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{ENV['BRANCH_TO_BUILD']}"
else
if os_type == 'Android'
version_number = android_get_version_name(gradle_file: './android/app/build.gradle')
build_number = android_get_version_code(gradle_file: './android/app/build.gradle')
elsif os_type == 'iOS'
version_number = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost')
build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
end
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
end
s3 = Aws::S3::Resource.new(region: s3_region)
file_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{file}")
file_obj.upload_file("#{file_path}")
if is_build_pr
if os_type == 'Android'
install_url = "https://#{s3_bucket}/#{s3_folder}/#{file}"
elsif os_type == 'iOS'
current_build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
plist_template = File.read('plist.erb')
plist_body = ERB.new(plist_template).result(binding)
plist_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{file_plist}")
plist_obj.put(body: plist_body)
install_url = "itms-services://?action=download-manifest&url=https://#{s3_bucket}/#{s3_folder}/#{file_plist}"
end
qa_build_message({
:os_type => os_type,
:install_url => install_url
})
end
public_link = "https://#{s3_bucket}/#{s3_folder}/#{file}"
if file == 'Mattermost-simulator-x86_64.app.zip'
pretext = '#### New iOS build for VM/Simulator'
msg = "Download link: #{public_link}"
send_message_to_mattermost({
:version_number => version_number,
:build_number => build_number,
:pretext => pretext,
:title => '',
:thumb_url => 'https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/move-to-ios-icon.png',
:msg => msg,
:default_payloads => [],
:success => true,
})
end
UI.success("S3 bucket @#{s3_bucket}, object @#{s3_folder}/#{file}")
UI.success("S3 public path: #{public_link}")
end
end
platform :ios do
before_all do
if ENV['CIRCLECI'] == 'true'
setup_circle_ci
end
end
desc 'Get iOS adhoc profiles'
lane :adhoc do
if ENV['MATCH_TYPE'] != 'adhoc' && !ENV['MATCH_PASSWORD'].nil? && !ENV['MATCH_PASSWORD'].empty?
match(
type: 'adhoc'
)
end
end
desc 'Build iOS app'
lane :build do
unless configured
@ -321,7 +411,7 @@ platform :ios do
)
# Sync the provisioning profiles using match
if ENV['SYNC_PROVISIONING_PROFILES'] == 'true'
if ENV['SYNC_PROVISIONING_PROFILES'] == 'true' && !ENV['MATCH_PASSWORD'].nil? && !ENV['MATCH_PASSWORD'].empty?
match(type: ENV['MATCH_TYPE'] || 'adhoc')
end
end
@ -333,6 +423,14 @@ platform :ios do
end
end
lane :deploy do |options|
ipa_path = options[:ipa]
unless ipa_path.nil?
submit_to_testflight(ipa_path)
end
end
error do |lane, exception|
version = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost')
build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
@ -348,12 +446,37 @@ platform :ios do
})
end
def setup_code_signing
disable_automatic_code_signing(path: './ios/Mattermost.xcodeproj')
ENV['MATCH_APP_IDENTIFIER'].split(',').each do |id|
target = 'Mattermost'
if id.include? 'NotificationService'
target = 'NotificationService'
elsif id.include? 'MattermostShare'
target = 'MattermostShare'
end
profile = "sigh_#{id}_#{ENV['MATCH_TYPE']}"
update_project_provisioning(
xcodeproj: './ios/Mattermost.xcodeproj',
profile: ENV["#{profile}_profile-path"], # optional if you use sigh
target_filter: ".*#{target}$", # matches name or type of a target
build_configuration: 'Release',
code_signing_identity: 'iPhone Distribution' # optionally specify the codesigning identity
)
end
end
def build_ios()
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
app_name_sub = app_name.gsub(" ", "_")
config_mode = ENV['BUILD_FOR_RELEASE'] == 'true' ? 'Release' : 'Debug'
method = ENV['IOS_BUILD_EXPORT_METHOD'].nil? || ENV['IOS_BUILD_EXPORT_METHOD'].empty? ? 'ad-hoc' : ENV['IOS_BUILD_EXPORT_METHOD']
setup_code_signing
gym(
clean: true,
scheme: 'Mattermost',
@ -362,6 +485,7 @@ platform :ios do
export_method: method,
skip_profile_detection: true,
output_name: "#{app_name_sub}.ipa",
export_xcargs: "-allowProvisioningUpdates",
export_options: {
signingStyle: 'manual',
iCloudContainerEnvironment: 'Production'
@ -483,6 +607,14 @@ platform :android do
end
end
lane :deploy do |options|
apk_path = options[:apk]
unless apk_path.nil?
submit_to_google_play(apk_path)
end
end
error do |lane, exception|
build_number = android_get_version_code(
gradle_file: './android/app/build.gradle'
@ -587,83 +719,102 @@ def send_message_to_mattermost(options)
end
end
def submit_to_testflight(ipa_path)
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
app_name_sub = app_name.gsub(" ", "_")
if(File.file?(ipa_path))
UI.success("ipa file #{ipa_path}")
upload_to_tesflight(
ipa: ipa_path
)
else
UI.user_error! "ipa file does not exist #{ipa_path}"
return
end
version_number = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost')
build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
public_link = "https://#{ENV['AWS_BUCKET_NAME']}/#{s3_folder}/#{app_name_sub}.ipa"
# Send a build message to Mattermost
pretext = '#### New iOS released ready to be published'
msg = "Release has been cut and is on TestFlight ready to be published.\nDownload link: #{public_link}"
if ENV['BETA_BUILD'] == 'true'
pretext = '#### New iOS beta published to TestFlight'
msg = "Sign up as a beta tester [here](https://testflight.apple.com/join/Q7Rx7K9P).\nDownload link: #{public_link}"
end
send_message_to_mattermost({
:version_number => version_number,
:build_number => build_number,
:pretext => pretext,
:title => '',
:thumb_url => 'https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/move-to-ios-icon.png',
:msg => msg,
:default_payloads => [],
:success => true,
})
end
def submit_to_google_play(apk_path)
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
app_name_sub = app_name.gsub(" ", "_")
if(File.file?(apk_path))
UI.success("apk file #{apk_path}")
unless ENV['SUPPLY_JSON_KEY'].nil? || ENV['SUPPLY_JSON_KEY'].empty?
supply(
track: ENV['SUPPLY_TRACK'] || 'alpha',
apk: apk_path
)
end
else
UI.user_error! "apk file does not exist #{apk_path}"
return
end
version_number = android_get_version_name(gradle_file: './android/app/build.gradle')
build_number = android_get_version_code(gradle_file: './android/app/build.gradle')
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
public_link = "https://#{ENV['AWS_BUCKET_NAME']}/#{s3_folder}/#{app_name_sub}.apk"
# Send a build message to Mattermost
pretext = '#### New Android released ready to be published'
msg = "Release has been cut and is on the Beta track ready to be published.\nDownload link: #{public_link}"
if ENV['BETA_BUILD'] == 'true'
pretext = '#### New Android beta published to Google Play'
msg = "Sign up as a beta tester [here](https://play.google.com/apps/testing/com.mattermost.rnbeta).\nDownload link: #{public_link}"
end
send_message_to_mattermost({
:version_number => version_number,
:build_number => build_number,
:pretext => pretext,
:title => '',
:thumb_url => 'https://lh3.ggpht.com/XL0CrI8skkxnboGct-duyg-bZ_MxJDTrjczyjdU8OP2PM1dmj7SP4jL1K8JQeMIB3AM=w300',
:msg => msg,
:default_payloads => [],
:success => true,
})
end
def submit_to_store
apk_path = lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]
ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH]
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
app_name_sub = app_name.gsub(" ", "_")
s3_region = ENV['AWS_REGION']
s3_bucket = ENV['AWS_BUCKET_NAME']
# Submit to Google Play if required
if !apk_path.nil? && ENV['SUBMIT_ANDROID_TO_GOOGLE_PLAY'] == 'true'
UI.success("apk file #{apk_path}")
supply(
track: ENV['SUPPLY_TRACK'] || 'alpha',
apk: apk_path
)
version_number = android_get_version_name(gradle_file: './android/app/build.gradle')
build_number = android_get_version_code(gradle_file: './android/app/build.gradle')
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
public_link = "https://#{s3_bucket}/#{s3_folder}/#{app_name_sub}.apk"
# Send a build message to Mattermost
pretext = '#### New Android released ready to be published'
msg = "Release has been cut and is on the Beta track ready to be published.\nDownload link: #{public_link}"
if ENV['BETA_BUILD'] == 'true'
pretext = '#### New Android beta published to Google Play'
msg = "Sign up as a beta tester [here](https://play.google.com/apps/testing/com.mattermost.rnbeta).\nDownload link: #{public_link}"
end
send_message_to_mattermost({
:version_number => version_number,
:build_number => build_number,
:pretext => pretext,
:title => '',
:thumb_url => 'https://lh3.ggpht.com/XL0CrI8skkxnboGct-duyg-bZ_MxJDTrjczyjdU8OP2PM1dmj7SP4jL1K8JQeMIB3AM=w300',
:msg => msg,
:default_payloads => [],
:success => true,
})
submit_to_google_play(apk_path)
end
# Submit to TestFlight if required
if !ipa_path.nil? && ENV['SUBMIT_IOS_TO_TESTFLIGHT'] == 'true'
UI.success("ipa file #{ipa_path}")
pilot
version_number = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost')
build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
public_link = "https://#{s3_bucket}/#{s3_folder}/#{app_name_sub}.ipa"
# Send a build message to Mattermost
pretext = '#### New iOS released ready to be published'
msg = "Release has been cut and is on TestFlight ready to be published.\nDownload link: #{public_link}"
if ENV['BETA_BUILD'] == 'true'
pretext = '#### New iOS beta published to TestFlight'
msg = "Sign up as a beta tester [here](https://testflight.apple.com/join/Q7Rx7K9P).\nDownload link: #{public_link}"
end
send_message_to_mattermost({
:version_number => version_number,
:build_number => build_number,
:pretext => pretext,
:title => '',
:thumb_url => 'https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/move-to-ios-icon.png',
:msg => msg,
:default_payloads => [],
:success => true,
})
submit_to_testflight(ipa_path)
end
end
@ -686,92 +837,3 @@ def qa_build_message(options)
UI.success("PR Built for #{os_type}: #{install_url}")
end
end
desc 'Upload file to s3'
lane :upload_file_to_s3 do |options|
os_type = options[:os_type]
file = options[:file]
file_plist = ""
if file.nil? || file.empty?
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
filename = app_name.gsub(" ", "_")
if os_type == 'Android'
file = "#{filename}.apk"
elsif os_type == 'iOS'
file = "#{filename}.ipa"
file_plist = "#{filename}.plist"
end
end
build_folder_path = Dir[File.expand_path('..')].first
file_path = "#{build_folder_path}/#{file}"
unless ENV['AWS_BUCKET_NAME'].nil? || ENV['AWS_BUCKET_NAME'].empty? || ENV['AWS_REGION'].nil? || ENV['AWS_REGION'].empty? || file_path.nil?
s3_region = ENV['AWS_REGION']
s3_bucket = ENV['AWS_BUCKET_NAME']
s3_folder = ''
version_number = ''
build_number = ''
if is_build_pr
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{ENV['BRANCH_TO_BUILD']}"
else
if os_type == 'Android'
version_number = android_get_version_name(gradle_file: './android/app/build.gradle')
build_number = android_get_version_code(gradle_file: './android/app/build.gradle')
elsif os_type == 'iOS'
version_number = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost')
build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
end
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
end
s3 = Aws::S3::Resource.new(region: s3_region)
file_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{file}")
file_obj.upload_file("#{file_path}")
if is_build_pr
if os_type == 'Android'
install_url = "https://#{s3_bucket}/#{s3_folder}/#{file}"
elsif os_type == 'iOS'
current_build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
plist_template = File.read('plist.erb')
plist_body = ERB.new(plist_template).result(binding)
plist_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{file_plist}")
plist_obj.put(body: plist_body)
install_url = "itms-services://?action=download-manifest&url=https://#{s3_bucket}/#{s3_folder}/#{file_plist}"
end
qa_build_message({
:os_type => os_type,
:install_url => install_url
})
end
public_link = "https://#{s3_bucket}/#{s3_folder}/#{file}"
if file == 'Mattermost-simulator-x86_64.app.zip'
pretext = '#### New iOS build for VM/Simulator'
msg = "Download link: #{public_link}"
send_message_to_mattermost({
:version_number => version_number,
:build_number => build_number,
:pretext => pretext,
:title => '',
:thumb_url => 'https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/move-to-ios-icon.png',
:msg => msg,
:default_payloads => [],
:success => true,
})
end
UI.success("S3 bucket @#{s3_bucket}, object @#{s3_folder}/#{file}")
UI.success("S3 public path: #{public_link}")
end
end

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeClient.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeDashManifestXML.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeError.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeKit.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger+Private.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeOperation.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubePlayerScript.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo+Private.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoOperation.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoWebpage.h

View file

@ -0,0 +1 @@
../../../YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeClient.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeError.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeKit.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeOperation.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoOperation.h

View file

@ -0,0 +1 @@
../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h

View file

@ -0,0 +1 @@
../../../YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.h

20
ios/Pods/Manifest.lock generated Normal file
View file

@ -0,0 +1,20 @@
PODS:
- XCDYouTubeKit (2.7.1)
- YoutubePlayer-in-WKWebView (0.3.3)
DEPENDENCIES:
- XCDYouTubeKit (= 2.7.1)
- YoutubePlayer-in-WKWebView (~> 0.3.1)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- XCDYouTubeKit
- YoutubePlayer-in-WKWebView
SPEC CHECKSUMS:
XCDYouTubeKit: c8567fd5cb388a3099fa26eee4b30df2a467847d
YoutubePlayer-in-WKWebView: 7694e858c5c3472ed067d6fe34eb9b944845e63c
PODFILE CHECKSUM: e565d3af8eabb0e489b73c79433e140e30f8fa9c
COCOAPODS: 1.5.3

893
ios/Pods/Pods.xcodeproj/project.pbxproj generated Normal file
View file

@ -0,0 +1,893 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
00EF717F2CE5C08CE046FAB25A49C579 /* Pods-MattermostTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = C0A3E27A51ED227A8460BDBC0B1A5BDC /* Pods-MattermostTests-dummy.m */; };
19A3D8279379A5ACBE7F699CB120E20C /* XCDYouTubeOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = A16570331366E84C38260E575C1ACDAF /* XCDYouTubeOperation.h */; settings = {ATTRIBUTES = (Project, ); }; };
1B069680B86677A2EA0846201573A2C9 /* XCDYouTubeClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 218BAF448A4CEBF30D9938922CA236C2 /* XCDYouTubeClient.h */; settings = {ATTRIBUTES = (Project, ); }; };
1C5F63B626D206B0ED32320876A3EA60 /* XCDYouTubeVideo.h in Headers */ = {isa = PBXBuildFile; fileRef = BB52596A2D243BBDCE8BCB3CF50A6E20 /* XCDYouTubeVideo.h */; settings = {ATTRIBUTES = (Project, ); }; };
2F6113A993C9CA73926D2946728A7D37 /* XCDYouTubeVideoPlayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DD5E806A91AFE8DD2B077AE695123547 /* XCDYouTubeVideoPlayerViewController.m */; };
32BC55AFE9DADF2BE266AB44A9F9C382 /* XCDYouTubeDashManifestXML.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A014D164D90DF54F9BD4AC60868D11D /* XCDYouTubeDashManifestXML.m */; };
3933FE5CEDAAFF0AE1DE7413EDA8CA38 /* XCDYouTubeDashManifestXML.h in Headers */ = {isa = PBXBuildFile; fileRef = 18BED515670B4F1D15806832C57D59F9 /* XCDYouTubeDashManifestXML.h */; settings = {ATTRIBUTES = (Project, ); }; };
3F0B35BE6C13ADDF8364D0E9F7A07522 /* XCDYouTubeKit-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = CFFE5759AD00493BC5172561B1C1C7CD /* XCDYouTubeKit-dummy.m */; };
4C91B4A27AFBF35591DD3CDF23E2DC17 /* XCDYouTubeError.h in Headers */ = {isa = PBXBuildFile; fileRef = 162E428722ECD765CEE83EB3D33C71E2 /* XCDYouTubeError.h */; settings = {ATTRIBUTES = (Project, ); }; };
5B8AEF4A577D21542D6F8DE2B248C8E7 /* XCDYouTubeVideoWebpage.m in Sources */ = {isa = PBXBuildFile; fileRef = 920F9C3CEECF4C1924F458E3D0428ED8 /* XCDYouTubeVideoWebpage.m */; };
5BE4F6E90A8A1644C0BBB4635AD0D84F /* WKYTPlayerView.h in Headers */ = {isa = PBXBuildFile; fileRef = 19281C3EF03E987F37C203D17BCA2EC5 /* WKYTPlayerView.h */; settings = {ATTRIBUTES = (Project, ); }; };
5ED62229AE2031D59F1DF033F77D03B2 /* XCDYouTubeVideoOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = BA781F6AB0F7487AD05BC63EA40AB8BF /* XCDYouTubeVideoOperation.m */; };
607B50452E483ACCA8FA33B3C0114BFD /* XCDYouTubeVideoOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 281750171C47F186466B86E65956736D /* XCDYouTubeVideoOperation.h */; settings = {ATTRIBUTES = (Project, ); }; };
6A881BEE202BC2AC64ACE1C2E7583756 /* XCDYouTubeVideo+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1CBB4B14F0B800EC1527A5353A3AE1CA /* XCDYouTubeVideo+Private.h */; settings = {ATTRIBUTES = (Project, ); }; };
7621E888B2755702A10B6724E4107332 /* XCDYouTubePlayerScript.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E94B5BEED415E94C72301A86E7A9859 /* XCDYouTubePlayerScript.m */; };
76BE93DCF558BFA281930A26018F9330 /* XCDYouTubeLogger+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DAF995C826738DB2D85603716D91154 /* XCDYouTubeLogger+Private.h */; settings = {ATTRIBUTES = (Project, ); }; };
77F5E0033E60972EC230A8ABD78071A3 /* XCDYouTubeVideo.m in Sources */ = {isa = PBXBuildFile; fileRef = EFFFFBEC2A07FFA8841667B371ED34E0 /* XCDYouTubeVideo.m */; };
80627FF4ECD0A58F8A6C2874FA612EB1 /* YoutubePlayer-in-WKWebView-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 72206B26A17824A476D334C5612D4142 /* YoutubePlayer-in-WKWebView-dummy.m */; };
8433D3049BEEF12D212D6B1DD6DD0512 /* XCDYouTubeClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 46714161B2C62428DB972729952DC352 /* XCDYouTubeClient.m */; };
90A4DC7BA3E2C3C54D484B8959CFB708 /* XCDYouTubePlayerScript.h in Headers */ = {isa = PBXBuildFile; fileRef = F1766C4F2FDC063F421CA2D4688FED68 /* XCDYouTubePlayerScript.h */; settings = {ATTRIBUTES = (Project, ); }; };
9EFEB7A7C37759A0328EB1B198E2C37D /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15D48C716A411AD7621E7125E083F54A /* MediaPlayer.framework */; };
A53E28B0072944DEE03E0D2DA20C0FA0 /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98C181471820795EDEAACBC10B84709A /* JavaScriptCore.framework */; };
C50CA7A8B2F6691C7E29BD7BC2733975 /* XCDYouTubeLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F752B1C4CE355EE8A9DA89A25D0AE73 /* XCDYouTubeLogger.m */; };
CA1EF66BB4AA70EFF1869D1FE0A373AE /* Pods-Mattermost-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 926D56CC36F536DEFF1A45331E070750 /* Pods-Mattermost-dummy.m */; };
D56363E3D25AE7C3C37948A2ED267EFC /* WKYTPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = A081D43F9928F93E8E7FCA268B6660DA /* WKYTPlayerView.m */; };
E74005FCD11C34B3705DE02F89D4028B /* XCDYouTubeKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 724042289C9DC57B313DD893EFD1AA54 /* XCDYouTubeKit.h */; settings = {ATTRIBUTES = (Project, ); }; };
EA6A2186A0767AD5C4A45579994658BD /* XCDYouTubeVideoPlayerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 294543F53C1E8CC7BECE8214C09765EE /* XCDYouTubeVideoPlayerViewController.h */; settings = {ATTRIBUTES = (Project, ); }; };
F99FDC5AFF20BC2A65AEE5A41209A42C /* XCDYouTubeVideoWebpage.h in Headers */ = {isa = PBXBuildFile; fileRef = 8540AD01026C33CA77834BD0EE077CDB /* XCDYouTubeVideoWebpage.h */; settings = {ATTRIBUTES = (Project, ); }; };
FB83D8680F87F8315167D84D2BEEDF6C /* XCDYouTubeLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 510EF6F5EB43790577352F6C8A0718B0 /* XCDYouTubeLogger.h */; settings = {ATTRIBUTES = (Project, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
165E90C48BE6DE526DE6EFE88F787BE2 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 191A114290607BBCF32141FA83AB9F6E;
remoteInfo = "Pods-Mattermost";
};
A2AEF4FA0A01D420BBD3AC4B1428E3FD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 5552A5DF4126A9D12B869B8272B40FF1;
remoteInfo = XCDYouTubeKit;
};
DB4FDEE7D3C70BEC4323EBE859F93B11 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = D5F98C908412D8333DECA6E74A2FC15E;
remoteInfo = "YoutubePlayer-in-WKWebView";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
0800A13BE9EB3366E47E9A990DDEF5A4 /* Pods-MattermostTests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-MattermostTests-acknowledgements.plist"; sourceTree = "<group>"; };
0AD2A25648B085555683131216BAD797 /* Pods-Mattermost-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-Mattermost-acknowledgements.markdown"; sourceTree = "<group>"; };
0CA88A7462704DBCB53C017185C1A65F /* Pods-Mattermost-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-Mattermost-acknowledgements.plist"; sourceTree = "<group>"; };
15D48C716A411AD7621E7125E083F54A /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/MediaPlayer.framework; sourceTree = DEVELOPER_DIR; };
162E428722ECD765CEE83EB3D33C71E2 /* XCDYouTubeError.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeError.h; path = XCDYouTubeKit/XCDYouTubeError.h; sourceTree = "<group>"; };
18BED515670B4F1D15806832C57D59F9 /* XCDYouTubeDashManifestXML.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeDashManifestXML.h; path = XCDYouTubeKit/XCDYouTubeDashManifestXML.h; sourceTree = "<group>"; };
19281C3EF03E987F37C203D17BCA2EC5 /* WKYTPlayerView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = WKYTPlayerView.h; path = WKYTPlayerView/WKYTPlayerView.h; sourceTree = "<group>"; };
1CBB4B14F0B800EC1527A5353A3AE1CA /* XCDYouTubeVideo+Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "XCDYouTubeVideo+Private.h"; path = "XCDYouTubeKit/XCDYouTubeVideo+Private.h"; sourceTree = "<group>"; };
218BAF448A4CEBF30D9938922CA236C2 /* XCDYouTubeClient.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeClient.h; path = XCDYouTubeKit/XCDYouTubeClient.h; sourceTree = "<group>"; };
24390EFD555DD124430DFF9724065945 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
281750171C47F186466B86E65956736D /* XCDYouTubeVideoOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeVideoOperation.h; path = XCDYouTubeKit/XCDYouTubeVideoOperation.h; sourceTree = "<group>"; };
294543F53C1E8CC7BECE8214C09765EE /* XCDYouTubeVideoPlayerViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeVideoPlayerViewController.h; path = XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h; sourceTree = "<group>"; };
377B2C391CA94B7612EDC96C2306DB48 /* Pods-MattermostTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-MattermostTests.release.xcconfig"; sourceTree = "<group>"; };
3BC2AF7C034FA52EAB440AC387B0F3E9 /* XCDYouTubeKit.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = XCDYouTubeKit.xcconfig; sourceTree = "<group>"; };
3DAF995C826738DB2D85603716D91154 /* XCDYouTubeLogger+Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "XCDYouTubeLogger+Private.h"; path = "XCDYouTubeKit/XCDYouTubeLogger+Private.h"; sourceTree = "<group>"; };
46714161B2C62428DB972729952DC352 /* XCDYouTubeClient.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubeClient.m; path = XCDYouTubeKit/XCDYouTubeClient.m; sourceTree = "<group>"; };
510EF6F5EB43790577352F6C8A0718B0 /* XCDYouTubeLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeLogger.h; path = XCDYouTubeKit/XCDYouTubeLogger.h; sourceTree = "<group>"; };
60FB83EAEF578DD0C2DE316FCF4322A9 /* Pods-MattermostTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-MattermostTests.debug.xcconfig"; sourceTree = "<group>"; };
6170F2E79BA4DE1619C6FD1F25C3901F /* Pods-Mattermost-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-Mattermost-frameworks.sh"; sourceTree = "<group>"; };
6CD50F1ED20084866B7CE47B8F8E4BE4 /* libYoutubePlayer-in-WKWebView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; name = "libYoutubePlayer-in-WKWebView.a"; path = "libYoutubePlayer-in-WKWebView.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6F752B1C4CE355EE8A9DA89A25D0AE73 /* XCDYouTubeLogger.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubeLogger.m; path = XCDYouTubeKit/XCDYouTubeLogger.m; sourceTree = "<group>"; };
72206B26A17824A476D334C5612D4142 /* YoutubePlayer-in-WKWebView-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "YoutubePlayer-in-WKWebView-dummy.m"; sourceTree = "<group>"; };
724042289C9DC57B313DD893EFD1AA54 /* XCDYouTubeKit.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeKit.h; path = XCDYouTubeKit/XCDYouTubeKit.h; sourceTree = "<group>"; };
7703139D81F98B8B6130B2DAFB831C41 /* libPods-Mattermost.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; name = "libPods-Mattermost.a"; path = "libPods-Mattermost.a"; sourceTree = BUILT_PRODUCTS_DIR; };
7A014D164D90DF54F9BD4AC60868D11D /* XCDYouTubeDashManifestXML.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubeDashManifestXML.m; path = XCDYouTubeKit/XCDYouTubeDashManifestXML.m; sourceTree = "<group>"; };
8540AD01026C33CA77834BD0EE077CDB /* XCDYouTubeVideoWebpage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeVideoWebpage.h; path = XCDYouTubeKit/XCDYouTubeVideoWebpage.h; sourceTree = "<group>"; };
8A89EF129E834187D884270CF5CAF3C5 /* Pods-Mattermost-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-Mattermost-resources.sh"; sourceTree = "<group>"; };
8E94B5BEED415E94C72301A86E7A9859 /* XCDYouTubePlayerScript.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubePlayerScript.m; path = XCDYouTubeKit/XCDYouTubePlayerScript.m; sourceTree = "<group>"; };
920F9C3CEECF4C1924F458E3D0428ED8 /* XCDYouTubeVideoWebpage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubeVideoWebpage.m; path = XCDYouTubeKit/XCDYouTubeVideoWebpage.m; sourceTree = "<group>"; };
926D56CC36F536DEFF1A45331E070750 /* Pods-Mattermost-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-Mattermost-dummy.m"; sourceTree = "<group>"; };
934A4B9575F4CBDCBE7B044C1F049365 /* Pods-Mattermost.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Mattermost.debug.xcconfig"; sourceTree = "<group>"; };
974866440990F0A14694BD9B2E9962E0 /* Pods-MattermostTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-MattermostTests-acknowledgements.markdown"; sourceTree = "<group>"; };
98C181471820795EDEAACBC10B84709A /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; };
A081D43F9928F93E8E7FCA268B6660DA /* WKYTPlayerView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = WKYTPlayerView.m; path = WKYTPlayerView/WKYTPlayerView.m; sourceTree = "<group>"; };
A16570331366E84C38260E575C1ACDAF /* XCDYouTubeOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeOperation.h; path = XCDYouTubeKit/XCDYouTubeOperation.h; sourceTree = "<group>"; };
A43B4ECF27212BC0465EDA19AE3164CC /* XCDYouTubeKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCDYouTubeKit-prefix.pch"; sourceTree = "<group>"; };
B564ED8191C54D3CAD8D2A2A6B9B9D1B /* Pods-Mattermost.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Mattermost.release.xcconfig"; sourceTree = "<group>"; };
BA781F6AB0F7487AD05BC63EA40AB8BF /* XCDYouTubeVideoOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubeVideoOperation.m; path = XCDYouTubeKit/XCDYouTubeVideoOperation.m; sourceTree = "<group>"; };
BB52596A2D243BBDCE8BCB3CF50A6E20 /* XCDYouTubeVideo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeVideo.h; path = XCDYouTubeKit/XCDYouTubeVideo.h; sourceTree = "<group>"; };
C0A3E27A51ED227A8460BDBC0B1A5BDC /* Pods-MattermostTests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-MattermostTests-dummy.m"; sourceTree = "<group>"; };
C70E5870375DC970207B78465E560E64 /* YoutubePlayer-in-WKWebView-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "YoutubePlayer-in-WKWebView-prefix.pch"; sourceTree = "<group>"; };
C99CF8DC55235B8BA857A47235948EE7 /* libXCDYouTubeKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; name = libXCDYouTubeKit.a; path = libXCDYouTubeKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
CA6DA7F1941EC2569E22ACF8A89FA03B /* Pods-MattermostTests-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-MattermostTests-resources.sh"; sourceTree = "<group>"; };
CFFE5759AD00493BC5172561B1C1C7CD /* XCDYouTubeKit-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "XCDYouTubeKit-dummy.m"; sourceTree = "<group>"; };
DD5E806A91AFE8DD2B077AE695123547 /* XCDYouTubeVideoPlayerViewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubeVideoPlayerViewController.m; path = XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.m; sourceTree = "<group>"; };
E0545ADDF3E1FF7C1BC19F497787C42B /* WKYTPlayerView.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; name = WKYTPlayerView.bundle; path = WKYTPlayerView/WKYTPlayerView.bundle; sourceTree = "<group>"; };
E4748ACD84B23087E6F590E0904FFEFB /* YoutubePlayer-in-WKWebView.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "YoutubePlayer-in-WKWebView.xcconfig"; sourceTree = "<group>"; };
E99B330F0F9A45FF2E1ACB80DA268841 /* libPods-MattermostTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; name = "libPods-MattermostTests.a"; path = "libPods-MattermostTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
EFFFFBEC2A07FFA8841667B371ED34E0 /* XCDYouTubeVideo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubeVideo.m; path = XCDYouTubeKit/XCDYouTubeVideo.m; sourceTree = "<group>"; };
F1766C4F2FDC063F421CA2D4688FED68 /* XCDYouTubePlayerScript.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubePlayerScript.h; path = XCDYouTubeKit/XCDYouTubePlayerScript.h; sourceTree = "<group>"; };
FFD8DC263483FE0689017EF3573C5C71 /* Pods-MattermostTests-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-MattermostTests-frameworks.sh"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
3BF9126BA0554EB8C75B0C9B1F5908D5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
9607CC185A314A5FB24F3B67E3C0B743 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A53E28B0072944DEE03E0D2DA20C0FA0 /* JavaScriptCore.framework in Frameworks */,
9EFEB7A7C37759A0328EB1B198E2C37D /* MediaPlayer.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
E909807276D8D8C1F62A49D70C2048F0 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
EBE0F2650665138130247C39F808CDFB /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1628BF05B4CAFDCC3549A101F5A10A17 /* Frameworks */ = {
isa = PBXGroup;
children = (
B01816E2D09E8070AEE9E96136D917B6 /* iOS */,
);
name = Frameworks;
sourceTree = "<group>";
};
3EAAB2CFFBD8598AB3144F85D9EDACD8 /* Pods-Mattermost */ = {
isa = PBXGroup;
children = (
0AD2A25648B085555683131216BAD797 /* Pods-Mattermost-acknowledgements.markdown */,
0CA88A7462704DBCB53C017185C1A65F /* Pods-Mattermost-acknowledgements.plist */,
926D56CC36F536DEFF1A45331E070750 /* Pods-Mattermost-dummy.m */,
6170F2E79BA4DE1619C6FD1F25C3901F /* Pods-Mattermost-frameworks.sh */,
8A89EF129E834187D884270CF5CAF3C5 /* Pods-Mattermost-resources.sh */,
934A4B9575F4CBDCBE7B044C1F049365 /* Pods-Mattermost.debug.xcconfig */,
B564ED8191C54D3CAD8D2A2A6B9B9D1B /* Pods-Mattermost.release.xcconfig */,
);
name = "Pods-Mattermost";
path = "Target Support Files/Pods-Mattermost";
sourceTree = "<group>";
};
428BC12C5AD7D04CCD5564A473A61275 /* Pods */ = {
isa = PBXGroup;
children = (
950725B3833769C44391B7E733E759D0 /* XCDYouTubeKit */,
6571058FEA94E8172ABEAFD4A1D3C1A7 /* YoutubePlayer-in-WKWebView */,
);
name = Pods;
sourceTree = "<group>";
};
6571058FEA94E8172ABEAFD4A1D3C1A7 /* YoutubePlayer-in-WKWebView */ = {
isa = PBXGroup;
children = (
19281C3EF03E987F37C203D17BCA2EC5 /* WKYTPlayerView.h */,
A081D43F9928F93E8E7FCA268B6660DA /* WKYTPlayerView.m */,
92B16AFE4649769630669C6CDA9910C9 /* Resources */,
C609F4F9C6363362CDC6B91DC2BB8DA7 /* Support Files */,
);
name = "YoutubePlayer-in-WKWebView";
path = "YoutubePlayer-in-WKWebView";
sourceTree = "<group>";
};
6CDFB0049112FA789537AC9B9E94FC6E /* Pods-MattermostTests */ = {
isa = PBXGroup;
children = (
974866440990F0A14694BD9B2E9962E0 /* Pods-MattermostTests-acknowledgements.markdown */,
0800A13BE9EB3366E47E9A990DDEF5A4 /* Pods-MattermostTests-acknowledgements.plist */,
C0A3E27A51ED227A8460BDBC0B1A5BDC /* Pods-MattermostTests-dummy.m */,
FFD8DC263483FE0689017EF3573C5C71 /* Pods-MattermostTests-frameworks.sh */,
CA6DA7F1941EC2569E22ACF8A89FA03B /* Pods-MattermostTests-resources.sh */,
60FB83EAEF578DD0C2DE316FCF4322A9 /* Pods-MattermostTests.debug.xcconfig */,
377B2C391CA94B7612EDC96C2306DB48 /* Pods-MattermostTests.release.xcconfig */,
);
name = "Pods-MattermostTests";
path = "Target Support Files/Pods-MattermostTests";
sourceTree = "<group>";
};
7D3B7C9338829DEF8BC3A52614FF35D6 /* Targets Support Files */ = {
isa = PBXGroup;
children = (
3EAAB2CFFBD8598AB3144F85D9EDACD8 /* Pods-Mattermost */,
6CDFB0049112FA789537AC9B9E94FC6E /* Pods-MattermostTests */,
);
name = "Targets Support Files";
sourceTree = "<group>";
};
92B16AFE4649769630669C6CDA9910C9 /* Resources */ = {
isa = PBXGroup;
children = (
E0545ADDF3E1FF7C1BC19F497787C42B /* WKYTPlayerView.bundle */,
);
name = Resources;
sourceTree = "<group>";
};
950725B3833769C44391B7E733E759D0 /* XCDYouTubeKit */ = {
isa = PBXGroup;
children = (
218BAF448A4CEBF30D9938922CA236C2 /* XCDYouTubeClient.h */,
46714161B2C62428DB972729952DC352 /* XCDYouTubeClient.m */,
18BED515670B4F1D15806832C57D59F9 /* XCDYouTubeDashManifestXML.h */,
7A014D164D90DF54F9BD4AC60868D11D /* XCDYouTubeDashManifestXML.m */,
162E428722ECD765CEE83EB3D33C71E2 /* XCDYouTubeError.h */,
724042289C9DC57B313DD893EFD1AA54 /* XCDYouTubeKit.h */,
510EF6F5EB43790577352F6C8A0718B0 /* XCDYouTubeLogger.h */,
6F752B1C4CE355EE8A9DA89A25D0AE73 /* XCDYouTubeLogger.m */,
3DAF995C826738DB2D85603716D91154 /* XCDYouTubeLogger+Private.h */,
A16570331366E84C38260E575C1ACDAF /* XCDYouTubeOperation.h */,
F1766C4F2FDC063F421CA2D4688FED68 /* XCDYouTubePlayerScript.h */,
8E94B5BEED415E94C72301A86E7A9859 /* XCDYouTubePlayerScript.m */,
BB52596A2D243BBDCE8BCB3CF50A6E20 /* XCDYouTubeVideo.h */,
EFFFFBEC2A07FFA8841667B371ED34E0 /* XCDYouTubeVideo.m */,
1CBB4B14F0B800EC1527A5353A3AE1CA /* XCDYouTubeVideo+Private.h */,
281750171C47F186466B86E65956736D /* XCDYouTubeVideoOperation.h */,
BA781F6AB0F7487AD05BC63EA40AB8BF /* XCDYouTubeVideoOperation.m */,
294543F53C1E8CC7BECE8214C09765EE /* XCDYouTubeVideoPlayerViewController.h */,
DD5E806A91AFE8DD2B077AE695123547 /* XCDYouTubeVideoPlayerViewController.m */,
8540AD01026C33CA77834BD0EE077CDB /* XCDYouTubeVideoWebpage.h */,
920F9C3CEECF4C1924F458E3D0428ED8 /* XCDYouTubeVideoWebpage.m */,
E6B7DAED273DD709FF4449921C0817D9 /* Support Files */,
);
name = XCDYouTubeKit;
path = XCDYouTubeKit;
sourceTree = "<group>";
};
A3D27D0A8AF193B0B5A20E3F139F18EE /* Products */ = {
isa = PBXGroup;
children = (
7703139D81F98B8B6130B2DAFB831C41 /* libPods-Mattermost.a */,
E99B330F0F9A45FF2E1ACB80DA268841 /* libPods-MattermostTests.a */,
C99CF8DC55235B8BA857A47235948EE7 /* libXCDYouTubeKit.a */,
6CD50F1ED20084866B7CE47B8F8E4BE4 /* libYoutubePlayer-in-WKWebView.a */,
);
name = Products;
sourceTree = "<group>";
};
B01816E2D09E8070AEE9E96136D917B6 /* iOS */ = {
isa = PBXGroup;
children = (
98C181471820795EDEAACBC10B84709A /* JavaScriptCore.framework */,
15D48C716A411AD7621E7125E083F54A /* MediaPlayer.framework */,
);
name = iOS;
sourceTree = "<group>";
};
C609F4F9C6363362CDC6B91DC2BB8DA7 /* Support Files */ = {
isa = PBXGroup;
children = (
E4748ACD84B23087E6F590E0904FFEFB /* YoutubePlayer-in-WKWebView.xcconfig */,
72206B26A17824A476D334C5612D4142 /* YoutubePlayer-in-WKWebView-dummy.m */,
C70E5870375DC970207B78465E560E64 /* YoutubePlayer-in-WKWebView-prefix.pch */,
);
name = "Support Files";
path = "../Target Support Files/YoutubePlayer-in-WKWebView";
sourceTree = "<group>";
};
CF1408CF629C7361332E53B88F7BD30C = {
isa = PBXGroup;
children = (
24390EFD555DD124430DFF9724065945 /* Podfile */,
1628BF05B4CAFDCC3549A101F5A10A17 /* Frameworks */,
428BC12C5AD7D04CCD5564A473A61275 /* Pods */,
A3D27D0A8AF193B0B5A20E3F139F18EE /* Products */,
7D3B7C9338829DEF8BC3A52614FF35D6 /* Targets Support Files */,
);
sourceTree = "<group>";
};
E6B7DAED273DD709FF4449921C0817D9 /* Support Files */ = {
isa = PBXGroup;
children = (
3BC2AF7C034FA52EAB440AC387B0F3E9 /* XCDYouTubeKit.xcconfig */,
CFFE5759AD00493BC5172561B1C1C7CD /* XCDYouTubeKit-dummy.m */,
A43B4ECF27212BC0465EDA19AE3164CC /* XCDYouTubeKit-prefix.pch */,
);
name = "Support Files";
path = "../Target Support Files/XCDYouTubeKit";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
121FA629058A6D7550BAA7E46A2DFF6C /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
1B069680B86677A2EA0846201573A2C9 /* XCDYouTubeClient.h in Headers */,
3933FE5CEDAAFF0AE1DE7413EDA8CA38 /* XCDYouTubeDashManifestXML.h in Headers */,
4C91B4A27AFBF35591DD3CDF23E2DC17 /* XCDYouTubeError.h in Headers */,
E74005FCD11C34B3705DE02F89D4028B /* XCDYouTubeKit.h in Headers */,
76BE93DCF558BFA281930A26018F9330 /* XCDYouTubeLogger+Private.h in Headers */,
FB83D8680F87F8315167D84D2BEEDF6C /* XCDYouTubeLogger.h in Headers */,
19A3D8279379A5ACBE7F699CB120E20C /* XCDYouTubeOperation.h in Headers */,
90A4DC7BA3E2C3C54D484B8959CFB708 /* XCDYouTubePlayerScript.h in Headers */,
6A881BEE202BC2AC64ACE1C2E7583756 /* XCDYouTubeVideo+Private.h in Headers */,
1C5F63B626D206B0ED32320876A3EA60 /* XCDYouTubeVideo.h in Headers */,
607B50452E483ACCA8FA33B3C0114BFD /* XCDYouTubeVideoOperation.h in Headers */,
EA6A2186A0767AD5C4A45579994658BD /* XCDYouTubeVideoPlayerViewController.h in Headers */,
F99FDC5AFF20BC2A65AEE5A41209A42C /* XCDYouTubeVideoWebpage.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
87BA6E865700D97E7F71638AAE9AFC0D /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
946AD953D8D82CF2E2C495E655189FD9 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B6DE8E634D9D8E06BFF5F56A21C33E98 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
5BE4F6E90A8A1644C0BBB4635AD0D84F /* WKYTPlayerView.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
191A114290607BBCF32141FA83AB9F6E /* Pods-Mattermost */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0A6512CB2B9DF1554E714C059EE642EE /* Build configuration list for PBXNativeTarget "Pods-Mattermost" */;
buildPhases = (
946AD953D8D82CF2E2C495E655189FD9 /* Headers */,
4A0A92E8C79C534671C264B96C2D3980 /* Sources */,
EBE0F2650665138130247C39F808CDFB /* Frameworks */,
);
buildRules = (
);
dependencies = (
32D9BECA4EDB7295613C7FBBFF1947E3 /* PBXTargetDependency */,
12B381BF70C519F8F5E118A06F0715F4 /* PBXTargetDependency */,
);
name = "Pods-Mattermost";
productName = "Pods-Mattermost";
productReference = 7703139D81F98B8B6130B2DAFB831C41 /* libPods-Mattermost.a */;
productType = "com.apple.product-type.library.static";
};
5552A5DF4126A9D12B869B8272B40FF1 /* XCDYouTubeKit */ = {
isa = PBXNativeTarget;
buildConfigurationList = A447275384E1E65FCC685DD176B14980 /* Build configuration list for PBXNativeTarget "XCDYouTubeKit" */;
buildPhases = (
121FA629058A6D7550BAA7E46A2DFF6C /* Headers */,
6E02437F69A69D78682AC11124EF1525 /* Sources */,
9607CC185A314A5FB24F3B67E3C0B743 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = XCDYouTubeKit;
productName = XCDYouTubeKit;
productReference = C99CF8DC55235B8BA857A47235948EE7 /* libXCDYouTubeKit.a */;
productType = "com.apple.product-type.library.static";
};
D5F98C908412D8333DECA6E74A2FC15E /* YoutubePlayer-in-WKWebView */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1F5DC73135AFED29F2A39052787E8E88 /* Build configuration list for PBXNativeTarget "YoutubePlayer-in-WKWebView" */;
buildPhases = (
B6DE8E634D9D8E06BFF5F56A21C33E98 /* Headers */,
BAD0507591933394D74E430744349DCA /* Sources */,
3BF9126BA0554EB8C75B0C9B1F5908D5 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = "YoutubePlayer-in-WKWebView";
productName = "YoutubePlayer-in-WKWebView";
productReference = 6CD50F1ED20084866B7CE47B8F8E4BE4 /* libYoutubePlayer-in-WKWebView.a */;
productType = "com.apple.product-type.library.static";
};
DE7906C32BF02EEC471C43D2EE24AA47 /* Pods-MattermostTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 42CD827C438E22B810D149D4E59AC5FC /* Build configuration list for PBXNativeTarget "Pods-MattermostTests" */;
buildPhases = (
87BA6E865700D97E7F71638AAE9AFC0D /* Headers */,
7FF2A957D32E6EED1BD925761A4422B4 /* Sources */,
E909807276D8D8C1F62A49D70C2048F0 /* Frameworks */,
);
buildRules = (
);
dependencies = (
2FADDBF2AD8381C1F61DFCE1A376150B /* PBXTargetDependency */,
);
name = "Pods-MattermostTests";
productName = "Pods-MattermostTests";
productReference = E99B330F0F9A45FF2E1ACB80DA268841 /* libPods-MattermostTests.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
BFDFE7DC352907FC980B868725387E98 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1100;
LastUpgradeCheck = 1100;
};
buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = CF1408CF629C7361332E53B88F7BD30C;
productRefGroup = A3D27D0A8AF193B0B5A20E3F139F18EE /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
191A114290607BBCF32141FA83AB9F6E /* Pods-Mattermost */,
DE7906C32BF02EEC471C43D2EE24AA47 /* Pods-MattermostTests */,
5552A5DF4126A9D12B869B8272B40FF1 /* XCDYouTubeKit */,
D5F98C908412D8333DECA6E74A2FC15E /* YoutubePlayer-in-WKWebView */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
4A0A92E8C79C534671C264B96C2D3980 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CA1EF66BB4AA70EFF1869D1FE0A373AE /* Pods-Mattermost-dummy.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
6E02437F69A69D78682AC11124EF1525 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8433D3049BEEF12D212D6B1DD6DD0512 /* XCDYouTubeClient.m in Sources */,
32BC55AFE9DADF2BE266AB44A9F9C382 /* XCDYouTubeDashManifestXML.m in Sources */,
3F0B35BE6C13ADDF8364D0E9F7A07522 /* XCDYouTubeKit-dummy.m in Sources */,
C50CA7A8B2F6691C7E29BD7BC2733975 /* XCDYouTubeLogger.m in Sources */,
7621E888B2755702A10B6724E4107332 /* XCDYouTubePlayerScript.m in Sources */,
77F5E0033E60972EC230A8ABD78071A3 /* XCDYouTubeVideo.m in Sources */,
5ED62229AE2031D59F1DF033F77D03B2 /* XCDYouTubeVideoOperation.m in Sources */,
2F6113A993C9CA73926D2946728A7D37 /* XCDYouTubeVideoPlayerViewController.m in Sources */,
5B8AEF4A577D21542D6F8DE2B248C8E7 /* XCDYouTubeVideoWebpage.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
7FF2A957D32E6EED1BD925761A4422B4 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
00EF717F2CE5C08CE046FAB25A49C579 /* Pods-MattermostTests-dummy.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
BAD0507591933394D74E430744349DCA /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D56363E3D25AE7C3C37948A2ED267EFC /* WKYTPlayerView.m in Sources */,
80627FF4ECD0A58F8A6C2874FA612EB1 /* YoutubePlayer-in-WKWebView-dummy.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
12B381BF70C519F8F5E118A06F0715F4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = "YoutubePlayer-in-WKWebView";
target = D5F98C908412D8333DECA6E74A2FC15E /* YoutubePlayer-in-WKWebView */;
targetProxy = DB4FDEE7D3C70BEC4323EBE859F93B11 /* PBXContainerItemProxy */;
};
2FADDBF2AD8381C1F61DFCE1A376150B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = "Pods-Mattermost";
target = 191A114290607BBCF32141FA83AB9F6E /* Pods-Mattermost */;
targetProxy = 165E90C48BE6DE526DE6EFE88F787BE2 /* PBXContainerItemProxy */;
};
32D9BECA4EDB7295613C7FBBFF1947E3 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = XCDYouTubeKit;
target = 5552A5DF4126A9D12B869B8272B40FF1 /* XCDYouTubeKit */;
targetProxy = A2AEF4FA0A01D420BBD3AC4B1428E3FD /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
0430B9AA2C1C5F6D02D86CB026100EE8 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = E4748ACD84B23087E6F590E0904FFEFB /* YoutubePlayer-in-WKWebView.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
GCC_PREFIX_HEADER = "Target Support Files/YoutubePlayer-in-WKWebView/YoutubePlayer-in-WKWebView-prefix.pch";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
OTHER_LDFLAGS = "";
OTHER_LIBTOOLFLAGS = "";
PRIVATE_HEADERS_FOLDER_PATH = "";
PRODUCT_MODULE_NAME = YoutubePlayer_in_WKWebView;
PRODUCT_NAME = "YoutubePlayer-in-WKWebView";
PUBLIC_HEADERS_FOLDER_PATH = "";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
161EADF999CB6B2B796E0054E074569D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 60FB83EAEF578DD0C2DE316FCF4322A9 /* Pods-MattermostTests.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
CLANG_ENABLE_OBJC_WEAK = NO;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
MACH_O_TYPE = staticlib;
OTHER_LDFLAGS = "";
OTHER_LIBTOOLFLAGS = "";
PODS_ROOT = "$(SRCROOT)";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
2D52F03349CA866E882EB87084FF9D21 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGNING_ALLOWED = NO;
CODE_SIGNING_REQUIRED = NO;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"POD_CONFIGURATION_DEBUG=1",
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
STRIP_INSTALLED_PRODUCT = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
SYMROOT = "${SRCROOT}/../build";
};
name = Debug;
};
3536F4235A4BE9CAF9A3A90CECC64AD5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = E4748ACD84B23087E6F590E0904FFEFB /* YoutubePlayer-in-WKWebView.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
GCC_PREFIX_HEADER = "Target Support Files/YoutubePlayer-in-WKWebView/YoutubePlayer-in-WKWebView-prefix.pch";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
OTHER_LDFLAGS = "";
OTHER_LIBTOOLFLAGS = "";
PRIVATE_HEADERS_FOLDER_PATH = "";
PRODUCT_MODULE_NAME = YoutubePlayer_in_WKWebView;
PRODUCT_NAME = "YoutubePlayer-in-WKWebView";
PUBLIC_HEADERS_FOLDER_PATH = "";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
45C68B99EB6B81E0DD1319B540AF6A4D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3BC2AF7C034FA52EAB440AC387B0F3E9 /* XCDYouTubeKit.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
GCC_PREFIX_HEADER = "Target Support Files/XCDYouTubeKit/XCDYouTubeKit-prefix.pch";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
OTHER_LDFLAGS = "";
OTHER_LIBTOOLFLAGS = "";
PRIVATE_HEADERS_FOLDER_PATH = "";
PRODUCT_MODULE_NAME = XCDYouTubeKit;
PRODUCT_NAME = XCDYouTubeKit;
PUBLIC_HEADERS_FOLDER_PATH = "";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
50A87C79F1E5C7C6BFADB42D9E50A087 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 377B2C391CA94B7612EDC96C2306DB48 /* Pods-MattermostTests.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
CLANG_ENABLE_OBJC_WEAK = NO;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
MACH_O_TYPE = staticlib;
OTHER_LDFLAGS = "";
OTHER_LIBTOOLFLAGS = "";
PODS_ROOT = "$(SRCROOT)";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
682764580BDEF953B0E72A847F082705 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 934A4B9575F4CBDCBE7B044C1F049365 /* Pods-Mattermost.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
CLANG_ENABLE_OBJC_WEAK = NO;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
MACH_O_TYPE = staticlib;
OTHER_LDFLAGS = "";
OTHER_LIBTOOLFLAGS = "";
PODS_ROOT = "$(SRCROOT)";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
86B3C2B708242AD4B00334BBE7D14EFB /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3BC2AF7C034FA52EAB440AC387B0F3E9 /* XCDYouTubeKit.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
GCC_PREFIX_HEADER = "Target Support Files/XCDYouTubeKit/XCDYouTubeKit-prefix.pch";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
OTHER_LDFLAGS = "";
OTHER_LIBTOOLFLAGS = "";
PRIVATE_HEADERS_FOLDER_PATH = "";
PRODUCT_MODULE_NAME = XCDYouTubeKit;
PRODUCT_NAME = XCDYouTubeKit;
PUBLIC_HEADERS_FOLDER_PATH = "";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
A0A8AE5FB77A90328CE173082A521CA4 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = B564ED8191C54D3CAD8D2A2A6B9B9D1B /* Pods-Mattermost.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
CLANG_ENABLE_OBJC_WEAK = NO;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
MACH_O_TYPE = staticlib;
OTHER_LDFLAGS = "";
OTHER_LIBTOOLFLAGS = "";
PODS_ROOT = "$(SRCROOT)";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
B45EE3CA30FA810239EAC87C682A2A52 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGNING_ALLOWED = NO;
CODE_SIGNING_REQUIRED = NO;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"POD_CONFIGURATION_RELEASE=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
STRIP_INSTALLED_PRODUCT = NO;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
SYMROOT = "${SRCROOT}/../build";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
0A6512CB2B9DF1554E714C059EE642EE /* Build configuration list for PBXNativeTarget "Pods-Mattermost" */ = {
isa = XCConfigurationList;
buildConfigurations = (
682764580BDEF953B0E72A847F082705 /* Debug */,
A0A8AE5FB77A90328CE173082A521CA4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
1F5DC73135AFED29F2A39052787E8E88 /* Build configuration list for PBXNativeTarget "YoutubePlayer-in-WKWebView" */ = {
isa = XCConfigurationList;
buildConfigurations = (
3536F4235A4BE9CAF9A3A90CECC64AD5 /* Debug */,
0430B9AA2C1C5F6D02D86CB026100EE8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
42CD827C438E22B810D149D4E59AC5FC /* Build configuration list for PBXNativeTarget "Pods-MattermostTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
161EADF999CB6B2B796E0054E074569D /* Debug */,
50A87C79F1E5C7C6BFADB42D9E50A087 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2D52F03349CA866E882EB87084FF9D21 /* Debug */,
B45EE3CA30FA810239EAC87C682A2A52 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
A447275384E1E65FCC685DD176B14980 /* Build configuration list for PBXNativeTarget "XCDYouTubeKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
45C68B99EB6B81E0DD1319B540AF6A4D /* Debug */,
86B3C2B708242AD4B00334BBE7D14EFB /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */;
}

View file

@ -0,0 +1,46 @@
# Acknowledgements
This application makes use of the following third party libraries:
## XCDYouTubeKit
The MIT License (MIT)
Copyright (c) 2013-2016 Cédric Luthi
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.
## YoutubePlayer-in-WKWebView
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Generated by CocoaPods - https://cocoapods.org

View file

@ -0,0 +1,84 @@
<?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>The MIT License (MIT)
Copyright (c) 2013-2016 Cédric Luthi
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>XCDYouTubeKit</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an 'AS IS' BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
</string>
<key>License</key>
<string>Apache</string>
<key>Title</key>
<string>YoutubePlayer-in-WKWebView</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>

View file

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_Pods_Mattermost : NSObject
@end
@implementation PodsDummy_Pods_Mattermost
@end

View file

@ -0,0 +1,146 @@
#!/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 [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
wait
fi

View file

@ -0,0 +1,124 @@
#!/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
}
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_resource "${PODS_ROOT}/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.bundle"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_resource "${PODS_ROOT}/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.bundle"
fi
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

View file

@ -0,0 +1,9 @@
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/XCDYouTubeKit" "${PODS_ROOT}/Headers/Public/YoutubePlayer-in-WKWebView"
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/XCDYouTubeKit" "${PODS_CONFIGURATION_BUILD_DIR}/YoutubePlayer-in-WKWebView"
OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/XCDYouTubeKit" -isystem "${PODS_ROOT}/Headers/Public/YoutubePlayer-in-WKWebView"
OTHER_LDFLAGS = $(inherited) -ObjC -l"XCDYouTubeKit" -l"YoutubePlayer-in-WKWebView" -framework "JavaScriptCore" -framework "MediaPlayer"
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

View file

@ -0,0 +1,9 @@
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/XCDYouTubeKit" "${PODS_ROOT}/Headers/Public/YoutubePlayer-in-WKWebView"
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/XCDYouTubeKit" "${PODS_CONFIGURATION_BUILD_DIR}/YoutubePlayer-in-WKWebView"
OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/XCDYouTubeKit" -isystem "${PODS_ROOT}/Headers/Public/YoutubePlayer-in-WKWebView"
OTHER_LDFLAGS = $(inherited) -ObjC -l"XCDYouTubeKit" -l"YoutubePlayer-in-WKWebView" -framework "JavaScriptCore" -framework "MediaPlayer"
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

View file

@ -0,0 +1,3 @@
# Acknowledgements
This application makes use of the following third party libraries:
Generated by CocoaPods - https://cocoapods.org

View file

@ -0,0 +1,29 @@
<?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>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>

View file

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_Pods_MattermostTests : NSObject
@end
@implementation PodsDummy_Pods_MattermostTests
@end

View file

@ -0,0 +1,146 @@
#!/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 [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
wait
fi

View file

@ -0,0 +1,118 @@
#!/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

View file

@ -0,0 +1,9 @@
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/XCDYouTubeKit" "${PODS_ROOT}/Headers/Public/YoutubePlayer-in-WKWebView"
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/XCDYouTubeKit" "${PODS_CONFIGURATION_BUILD_DIR}/YoutubePlayer-in-WKWebView"
OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/XCDYouTubeKit" -isystem "${PODS_ROOT}/Headers/Public/YoutubePlayer-in-WKWebView"
OTHER_LDFLAGS = $(inherited) -ObjC -framework "JavaScriptCore" -framework "MediaPlayer"
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

View file

@ -0,0 +1,9 @@
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/XCDYouTubeKit" "${PODS_ROOT}/Headers/Public/YoutubePlayer-in-WKWebView"
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/XCDYouTubeKit" "${PODS_CONFIGURATION_BUILD_DIR}/YoutubePlayer-in-WKWebView"
OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/XCDYouTubeKit" -isystem "${PODS_ROOT}/Headers/Public/YoutubePlayer-in-WKWebView"
OTHER_LDFLAGS = $(inherited) -ObjC -framework "JavaScriptCore" -framework "MediaPlayer"
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

View file

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_XCDYouTubeKit : NSObject
@end
@implementation PodsDummy_XCDYouTubeKit
@end

View file

@ -0,0 +1,12 @@
#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

View file

@ -0,0 +1,10 @@
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/XCDYouTubeKit
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/XCDYouTubeKit" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/XCDYouTubeKit"
OTHER_LDFLAGS = -framework "JavaScriptCore" -framework "MediaPlayer"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/XCDYouTubeKit
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES

View file

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_YoutubePlayer_in_WKWebView : NSObject
@end
@implementation PodsDummy_YoutubePlayer_in_WKWebView
@end

View file

@ -0,0 +1,12 @@
#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

View file

@ -0,0 +1,9 @@
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YoutubePlayer-in-WKWebView
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/YoutubePlayer-in-WKWebView" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/YoutubePlayer-in-WKWebView"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/YoutubePlayer-in-WKWebView
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES

22
ios/Pods/XCDYouTubeKit/LICENSE generated Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2013-2016 Cédric Luthi
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.

151
ios/Pods/XCDYouTubeKit/README.md generated Normal file
View file

@ -0,0 +1,151 @@
## About
[![Build Status](https://img.shields.io/circleci/project/0xced/XCDYouTubeKit/develop.svg?style=flat)](https://circleci.com/gh/0xced/XCDYouTubeKit)
[![Coverage Status](https://img.shields.io/codecov/c/github/0xced/XCDYouTubeKit/develop.svg?style=flat)](https://codecov.io/gh/0xced/XCDYouTubeKit/branch/develop)
[![Platform](https://img.shields.io/cocoapods/p/XCDYouTubeKit.svg?style=flat)](http://cocoadocs.org/docsets/XCDYouTubeKit/)
[![Pod Version](https://img.shields.io/cocoapods/v/XCDYouTubeKit.svg?style=flat)](https://cocoapods.org/pods/XCDYouTubeKit)
[![Carthage Compatibility](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage/)
[![License](https://img.shields.io/cocoapods/l/XCDYouTubeKit.svg?style=flat)](LICENSE)
**XCDYouTubeKit** is a YouTube video player for iOS, tvOS and macOS.
<img src="Screenshots/XCDYouTubeVideoPlayerViewController.png" width="480" height="320">
Are you enjoying XCDYouTubeKit? You can say thank you with [a tweet](https://twitter.com/intent/tweet?text=%400xced%20Thank%20you%20for%20XCDYouTubeKit%2E). I am also accepting donations. ;-)
[![Donate button](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MGEPRSNQFMV3W)
## Requirements
- Runs on iOS 8.0 and later
- Runs on macOS 10.9 and later
- Runs on tvOS 9.0 and later
## Warning
XCDYouTubeKit is against the YouTube [Terms of Service](https://www.youtube.com/t/terms). The only *official* way of playing a YouTube video inside an app is with a web view and the [iframe player API](https://developers.google.com/youtube/iframe_api_reference). Unfortunately, this is very slow and quite ugly, so I wrote this player to give users a better viewing experience.
## Installation
XCDYouTubeKit is available through CocoaPods and Carthage.
CocoaPods:
```ruby
pod "XCDYouTubeKit", "~> 2.7"
```
Carthage:
```objc
github "0xced/XCDYouTubeKit" ~> 2.7
```
Alternatively, you can manually use the provided static library or dynamic framework. In order to use the static library, you must:
1. Create a workspace (File → New → Workspace…)
2. Add your project to the workspace
3. Add the XCDYouTubeKit project to the workspace
4. Drag and drop the `libXCDYouTubeKit.a` file referenced from XCDYouTubeKit → Products → libXCDYouTubeKit.a into the *Link Binary With Libraries* build phase of your apps target.
These steps will ensure that `#import <XCDYouTubeKit/XCDYouTubeKit.h>` will work properly in your project.
## Usage
XCDYouTubeKit is [fully documented](http://cocoadocs.org/docsets/XCDYouTubeKit/).
### iOS 8.0+ & tvOS (AVPlayerViewController)
```objc
AVPlayerViewController *playerViewController = [AVPlayerViewController new];
[self presentViewController:playerViewController animated:YES completion:nil];
__weak AVPlayerViewController *weakPlayerViewController = playerViewController;
[[XCDYouTubeClient defaultClient] getVideoWithIdentifier:videoIdentifier completionHandler:^(XCDYouTubeVideo * _Nullable video, NSError * _Nullable error) {
if (video)
{
NSDictionary *streamURLs = video.streamURLs;
NSURL *streamURL = streamURLs[XCDYouTubeVideoQualityHTTPLiveStreaming] ?: streamURLs[@(XCDYouTubeVideoQualityHD720)] ?: streamURLs[@(XCDYouTubeVideoQualityMedium360)] ?: streamURLs[@(XCDYouTubeVideoQualitySmall240)];
weakPlayerViewController.player = [AVPlayer playerWithURL:streamURL];
[weakPlayerViewController.player play];
}
else
{
[self dismissViewControllerAnimated:YES completion:nil];
}
}];
```
### iOS, tvOS and macOS
```objc
NSString *videoIdentifier = @"9bZkp7q19f0"; // A 11 characters YouTube video identifier
[[XCDYouTubeClient defaultClient] getVideoWithIdentifier:videoIdentifier completionHandler:^(XCDYouTubeVideo *video, NSError *error) {
if (video)
{
// Do something with the `video` object
}
else
{
// Handle error
}
}];
```
### iOS 8.0
On iOS, you can use the class `XCDYouTubeVideoPlayerViewController` the same way you use a `MPMoviePlayerViewController`, except you initialize it with a YouTube video identifier instead of a content URL.
#### Present the video in full-screen
```objc
- (void) playVideo
{
XCDYouTubeVideoPlayerViewController *videoPlayerViewController = [[XCDYouTubeVideoPlayerViewController alloc] initWithVideoIdentifier:@"9bZkp7q19f0"];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayerPlaybackDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:videoPlayerViewController.moviePlayer];
[self presentMoviePlayerViewControllerAnimated:videoPlayerViewController];
}
- (void) moviePlayerPlaybackDidFinish:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:notification.object];
MPMovieFinishReason finishReason = [notification.userInfo[MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] integerValue];
if (finishReason == MPMovieFinishReasonPlaybackError)
{
NSError *error = notification.userInfo[XCDMoviePlayerPlaybackDidFinishErrorUserInfoKey];
// Handle error
}
}
```
#### Present the video in a non full-screen view
```objc
XCDYouTubeVideoPlayerViewController *videoPlayerViewController = [[XCDYouTubeVideoPlayerViewController alloc] initWithVideoIdentifier:@"9bZkp7q19f0"];
[videoPlayerViewController presentInView:self.videoContainerView];
[videoPlayerViewController.moviePlayer play];
```
See the demo project for more sample code.
## Logging
Since version 2.2.0, XCDYouTubeKit produces logs. XCDYouTubeKit supports [CocoaLumberjack](https://github.com/CocoaLumberjack/CocoaLumberjack) but does not require it.
See the `XCDYouTubeLogger` class [documentation](http://cocoadocs.org/docsets/XCDYouTubeKit/) for more information.
## Credits
The URL extraction algorithms in *XCDYouTubeKit* are inspired by the [YouTube extractor](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/youtube.py) module of the *youtube-dl* project.
## Contact
Cédric Luthi
- http://github.com/0xced
- http://twitter.com/0xced
## License
XCDYouTubeKit is available under the MIT license. See the [LICENSE](LICENSE) file for more information.

View file

@ -0,0 +1,98 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#if !__has_feature(nullability)
#define NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_END
#define nullable
#define __nullable
#endif
#import <Foundation/Foundation.h>
#import <XCDYouTubeKit/XCDYouTubeOperation.h>
#import <XCDYouTubeKit/XCDYouTubeVideo.h>
#import <XCDYouTubeKit/XCDYouTubeError.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The `XCDYouTubeClient` class is responsible for interacting with the YouTube API. Given a YouTube video identifier, you will get video information with the `<-getVideoWithIdentifier:completionHandler:>` method.
*
* On iOS, you probably dont want to use `XCDYouTubeClient` directly but the higher level class `<XCDYouTubeVideoPlayerViewController>`.
*/
@interface XCDYouTubeClient : NSObject
/**
* ------------------
* @name Initializing
* ------------------
*/
/**
* Returns the shared client with the default language, i.e. the preferred language of the main bundle.
*
* @return The default client.
*/
+ (instancetype) defaultClient;
/**
* Initializes a client with the specified language identifier.
*
* @param languageIdentifier An [ISO 639-1 two-letter language code](http://www.loc.gov/standards/iso639-2/php/code_list.php) used for error localization. If you pass a nil language identifier, the preferred language of the main bundle will be used.
*
* @return A client with the specified language identifier.
*/
- (instancetype) initWithLanguageIdentifier:(nullable NSString *)languageIdentifier;
/**
* ---------------------------------
* @name Accessing client properties
* ---------------------------------
*/
/**
* The language identifier of the client, used for error localization.
*
* @see -initWithLanguageIdentifier:
*/
@property (nonatomic, readonly) NSString *languageIdentifier;
/**
* --------------------------------------
* @name Interacting with the YouTube API
* --------------------------------------
*/
/**
* Starts an asynchronous operation for the specified video identifier, and calls a handler upon completion.
*
* @param videoIdentifier A 11 characters YouTube video identifier. If the video identifier is invalid (including nil) the completion handler will be called with an error with `XCDYouTubeVideoErrorDomain` domain and `XCDYouTubeErrorInvalidVideoIdentifier` code.
* @param completionHandler A block to execute when the client finishes the operation. The completion handler is executed on the main thread. If the completion handler is nil, this method throws an exception.
*
* @discussion If the operation completes successfully, the video parameter of the handler block contains a `<XCDYouTubeVideo>` object, and the error parameter is nil. If the operation fails, the video parameter is nil and the error parameter contains information about the failure. The error's domain is always `XCDYouTubeVideoErrorDomain`.
*
* @see XCDYouTubeErrorCode
*
* @return An opaque object conforming to the `<XCDYouTubeOperation>` protocol for canceling the asynchronous video information operation. If you call the `cancel` method before the operation is finished, the completion handler will not be called. It is recommended that you store this opaque object as a weak property.
*/
- (id<XCDYouTubeOperation>) getVideoWithIdentifier:(nullable NSString *)videoIdentifier completionHandler:(void (^)(XCDYouTubeVideo * __nullable video, NSError * __nullable error))completionHandler;
/**
* Starts an asynchronous operation for the specified video identifier, and calls a handler upon completion.
*
* @param videoIdentifier A 11 characters YouTube video identifier. If the video identifier is invalid (including nil) the completion handler will be called with an error with `XCDYouTubeVideoErrorDomain` domain and `XCDYouTubeErrorInvalidVideoIdentifier` code.
* @param cookies An array of `NSHTTPCookie` objects, can be nil. These cookies can be used for certain videos that require a login.
* @param completionHandler A block to execute when the client finishes the operation. The completion handler is executed on the main thread. If the completion handler is nil, this method throws an exception.
*
* @discussion If the operation completes successfully, the video parameter of the handler block contains a `<XCDYouTubeVideo>` object, and the error parameter is nil. If the operation fails, the video parameter is nil and the error parameter contains information about the failure. The error's domain is always `XCDYouTubeVideoErrorDomain`.
*
* @see XCDYouTubeErrorCode
*
* @return An opaque object conforming to the `<XCDYouTubeOperation>` protocol for canceling the asynchronous video information operation. If you call the `cancel` method before the operation is finished, the completion handler will not be called. It is recommended that you store this opaque object as a weak property.
*/
- (id<XCDYouTubeOperation>) getVideoWithIdentifier:(NSString *)videoIdentifier cookies:(nullable NSArray <NSHTTPCookie *>*)cookies completionHandler:(void (^)(XCDYouTubeVideo * __nullable video, NSError * __nullable error))completionHandler;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,83 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import "XCDYouTubeClient.h"
#import "XCDYouTubeVideoOperation.h"
@interface XCDYouTubeClient ()
@property (nonatomic, strong) NSOperationQueue *queue;
@end
@implementation XCDYouTubeClient
@synthesize languageIdentifier = _languageIdentifier;
+ (instancetype) defaultClient
{
static XCDYouTubeClient *defaultClient;
static dispatch_once_t once;
dispatch_once(&once, ^{
defaultClient = [self new];
});
return defaultClient;
}
- (instancetype) init
{
return [self initWithLanguageIdentifier:nil];
}
- (instancetype) initWithLanguageIdentifier:(NSString *)languageIdentifier
{
if (!(self = [super init]))
return nil; // LCOV_EXCL_LINE
_languageIdentifier = ^{
return languageIdentifier ?: ^{
NSArray *preferredLocalizations = [[NSBundle mainBundle] preferredLocalizations];
NSString *preferredLocalization = preferredLocalizations.firstObject ?: @"en";
return [NSLocale canonicalLanguageIdentifierFromString:preferredLocalization] ?: @"en";
}();
}();
_queue = [NSOperationQueue new];
_queue.maxConcurrentOperationCount = 6; // paul_irish: Chrome re-confirmed that the 6 connections-per-host limit is the right magic number: https://code.google.com/p/chromium/issues/detail?id=285567#c14 [https://twitter.com/paul_irish/status/422808635698212864]
_queue.name = NSStringFromClass(self.class);
return self;
}
- (id<XCDYouTubeOperation>) getVideoWithIdentifier:(NSString *)videoIdentifier cookies:(NSArray<NSHTTPCookie *> *)cookies completionHandler:(void (^)(XCDYouTubeVideo * _Nullable, NSError * _Nullable))completionHandler
{
if (!completionHandler)
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"The `completionHandler` argument must not be nil." userInfo:nil];
XCDYouTubeVideoOperation *operation = [[XCDYouTubeVideoOperation alloc] initWithVideoIdentifier:videoIdentifier languageIdentifier:self.languageIdentifier cookies:cookies];
operation.completionBlock = ^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
if (operation.video || operation.error)
{
NSAssert(!(operation.video && operation.error), @"One of `video` or `error` must be nil.");
completionHandler(operation.video, operation.error);
}
else
{
NSAssert(operation.isCancelled, @"Both `video` and `error` can not be nil if the operation was not canceled.");
}
operation.completionBlock = nil;
#pragma clang diagnostic pop
}];
};
[self.queue addOperation:operation];
return operation;
}
- (id<XCDYouTubeOperation>) getVideoWithIdentifier:(NSString *)videoIdentifier completionHandler:(void (^)(XCDYouTubeVideo * __nullable video, NSError * __nullable error))completionHandler
{
return [self getVideoWithIdentifier:videoIdentifier cookies:nil completionHandler:completionHandler];
}
@end

View file

@ -0,0 +1,17 @@
//
// XCDYouTubeDashManifestXML.h
// XCDYouTubeKit
//
// Created by Soneé John on 10/24/17.
// Copyright © 2017 Cédric Luthi. All rights reserved.
//
#import <Foundation/Foundation.h>
__attribute__((visibility("hidden")))
@interface XCDYouTubeDashManifestXML : NSObject
- (instancetype)initWithXMLString:(NSString *)XMLString;
- (NSDictionary *)streamURLs;
@end

View file

@ -0,0 +1,98 @@
//
// XCDYouTubeDashManifestXML.m
// XCDYouTubeKit
//
// Created by Soneé John on 10/24/17.
// Copyright © 2017 Cédric Luthi. All rights reserved.
//
#import "XCDYouTubeDashManifestXML.h"
@interface XCDYouTubeDashManifestXML()
@property (nonatomic, readonly) NSString *XMLString;
@end
@implementation XCDYouTubeDashManifestXML
- (instancetype)initWithXMLString:(NSString *)XMLString
{
if (!(self = [super init]))
return nil; // LCOV_EXCL_LINE
_XMLString = XMLString;
return self;
}
- (NSDictionary *)streamURLs
{
//Catch the type
NSError *xmlTypeRegexError = NULL;
NSRegularExpression *xmlTypeRegex = [NSRegularExpression regularExpressionWithPattern:@"(?<=(type=\"))(\\w|\\d|\\n|[().,\\-:;@#$%^&*\\[\\]\"'+/\\/®°⁰!?{}|`~]| )+?(?=(\"))" options:NSRegularExpressionAnchorsMatchLines error:&xmlTypeRegexError];
if (xmlTypeRegexError)
return nil;
NSTextCheckingResult *xmlTypeRegexCheckingResult = [xmlTypeRegex firstMatchInString:self.XMLString options:0 range:NSMakeRange(0, self.XMLString.length)];
NSString *xmlType = [self.XMLString substringWithRange:xmlTypeRegexCheckingResult.range];
NSRange staticRange = [xmlType rangeOfString:@"static" options:0];
if (staticRange.location == NSNotFound)
return nil;
//Do not process manifests that have DRM protection
NSRange contentProtectionRange = [self.XMLString rangeOfString:@"ContentProtection" options:0];
NSRange mp4ProtectionRange = [self.XMLString rangeOfString:@"mp4protection" options:0];
if (contentProtectionRange.location != NSNotFound || mp4ProtectionRange.location != NSNotFound)
return nil;
//Catch all URLs
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(?<=(<BaseURL>))(\\w|\\d|\\n|[().,\\-:;@#$%^&*\\[\\]\"'+/\\/®°!?{}|`~]| )+?(?=(</BaseURL>))" options:0 error:&error];
if (error)
return nil;
NSArray *checkingResults = [regex matchesInString:self.XMLString options:0 range:NSMakeRange(0, [self.XMLString length])];
if (checkingResults.count == 0 || checkingResults == nil)
return nil;
NSMutableArray <NSURL *>*URLs = [NSMutableArray new];
NSMutableDictionary *streamURLs = [NSMutableDictionary new];
for (NSTextCheckingResult *checkingResult in checkingResults)
{
NSString* substringForMatch = [self.XMLString substringWithRange:checkingResult.range];
NSURL *url = [NSURL URLWithString:substringForMatch];
NSRange youtubeRange = [url.absoluteString rangeOfString:@"youtube" options:0];
NSRange itagnRange = [url.absoluteString rangeOfString:@"itag" options:0];
if (youtubeRange.location != NSNotFound && itagnRange.location != NSNotFound )
{
[URLs addObject:url];
}
}
for (NSURL *url in URLs)
{
NSError *itagRegexError = nil;
NSRegularExpression *itagRegex = [NSRegularExpression regularExpressionWithPattern:@"(?<=(/itag/))(\\w|\\d|\\n|[().,\\-:;@#$%^&*\\[\\]\"'+/\\/®°!?{}|`~]| )+?(?=(/))" options:NSRegularExpressionAnchorsMatchLines error:&itagRegexError];
if (itagRegexError)
continue;
NSTextCheckingResult *itagCheckingResult = [itagRegex firstMatchInString:(NSString *_Nonnull)url.absoluteString options:0 range:NSMakeRange(0, url.absoluteString.length)];
NSString *itag = [url.absoluteString substringWithRange:itagCheckingResult.range];
streamURLs[@(itag.integerValue)] = url;
}
if (streamURLs.count == 0)
return nil;
return streamURLs;
}
@end

View file

@ -0,0 +1,47 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* The error domain used throughout XCDYouTubeKit.
*/
extern NSString *const XCDYouTubeVideoErrorDomain;
/**
* A key that may be present in the error's userInfo dictionary when the error code is XCDYouTubeErrorRestrictedPlayback.
* The object for that key is a NSSet instance containing localized country names.
*/
extern NSString *const XCDYouTubeAllowedCountriesUserInfoKey;
/**
* These values are returned as the error code property of an NSError object with the domain `XCDYouTubeVideoErrorDomain`.
*/
typedef NS_ENUM(NSInteger, XCDYouTubeErrorCode) {
/**
* Returned when no suitable video stream is available.
*/
XCDYouTubeErrorNoStreamAvailable = -2,
/**
* Returned when a network error occurs. See `NSUnderlyingErrorKey` in the userInfo dictionary for more information.
*/
XCDYouTubeErrorNetwork = -1,
/**
* Returned when the given video identifier string is invalid.
*/
XCDYouTubeErrorInvalidVideoIdentifier = 2,
/**
* Previously returned when the video was removed as a violation of YouTube's policy or when the video did not exist.
* Now replaced by code 150, i.e. `XCDYouTubeErrorRestrictedPlayback`.
*/
XCDYouTubeErrorRemovedVideo DEPRECATED_MSG_ATTRIBUTE("YouTube has stopped using error code 100.") = 100,
/**
* Returned when the video is not playable because of legal reasons or when the video is private.
*/
XCDYouTubeErrorRestrictedPlayback = 150
};

View file

@ -0,0 +1,16 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import <TargetConditionals.h>
#import <XCDYouTubeKit/XCDYouTubeClient.h>
#import <XCDYouTubeKit/XCDYouTubeError.h>
#import <XCDYouTubeKit/XCDYouTubeLogger.h>
#import <XCDYouTubeKit/XCDYouTubeOperation.h>
#import <XCDYouTubeKit/XCDYouTubeVideo.h>
#import <XCDYouTubeKit/XCDYouTubeVideoOperation.h>
#if TARGET_OS_IOS || (!defined(TARGET_OS_IOS) && TARGET_OS_IPHONE)
#import <XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h>
#endif

View file

@ -0,0 +1,21 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "XCDYouTubeLogger.h"
@interface XCDYouTubeLogger ()
+ (void) logMessage:(NSString * (^)(void))message level:(XCDLogLevel)level file:(const char *)file function:(const char *)function line:(NSUInteger)line;
@end
#define XCDYouTubeLog(_level, _message) [XCDYouTubeLogger logMessage:(_message) level:(_level) file:__FILE__ function:__PRETTY_FUNCTION__ line:__LINE__]
#define XCDYouTubeLogError(format, ...) XCDYouTubeLog(XCDLogLevelError, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; }))
#define XCDYouTubeLogWarning(format, ...) XCDYouTubeLog(XCDLogLevelWarning, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; }))
#define XCDYouTubeLogInfo(format, ...) XCDYouTubeLog(XCDLogLevelInfo, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; }))
#define XCDYouTubeLogDebug(format, ...) XCDYouTubeLog(XCDLogLevelDebug, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; }))
#define XCDYouTubeLogVerbose(format, ...) XCDYouTubeLog(XCDLogLevelVerbose, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; }))

View file

@ -0,0 +1,103 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* The [context][1] used when logging with CocoaLumberjack.
*
* [1]: https://github.com/CocoaLumberjack/CocoaLumberjack/blob/master/Documentation/CustomContext.md
*/
extern const NSInteger XCDYouTubeKitLumberjackContext;
/**
* The log levels, closely mirroring the log levels of CocoaLumberjack.
*/
typedef NS_ENUM(NSUInteger, XCDLogLevel) {
/**
* Used when an error is produced, e.g. when a `<XCDYouTubeVideoOperation>` finishes with an error.
*/
XCDLogLevelError = 0,
/**
* Used on unusual conditions that may eventually lead to an error.
*/
XCDLogLevelWarning = 1,
/**
* Used when logging normal operational information, e.g. when a `<XCDYouTubeVideoOperation>` starts, is cancelled or finishes.
*/
XCDLogLevelInfo = 2,
/**
* Used throughout a `<XCDYouTubeVideoOperation>` for debugging purpose, e.g. for HTTP requests.
*/
XCDLogLevelDebug = 3,
/**
* Used to report large amount of information, e.g. full HTTP responses.
*/
XCDLogLevelVerbose = 4,
};
/**
* You can use the `XCDYouTubeLogger` class to configure how the XCDYouTubeKit framework emits logs.
*
* By default, logs are emitted through CocoaLumberjack if it is available, i.e. if the `DDLog` class is found at runtime.
* The [context][1] used for CocoaLumberjack is the `XCDYouTubeKitLumberjackContext` constant whose value is `(NSInteger)0xced70676`.
*
* If CocoaLumberjack is not available, logs are emitted with `NSLog`, prefixed with the `[XCDYouTubeKit]` string.
*
* ## Controlling log levels
*
* If you are using CocoaLumberjack, you are responsible for controlling the log levels with the CocoaLumberjack APIs.
*
* If you are not using CocoaLumberjack, you can control the log levels with the `XCDYouTubeKitLogLevel` environment variable. See also the `<XCDLogLevel>` enum.
*
* Level | Value | Mask
* --------|-------|------
* Error | 0 | 0x01
* Warning | 1 | 0x02
* Info | 2 | 0x04
* Debug | 3 | 0x08
* Verbose | 4 | 0x10
*
* Use the corresponding bitmask to combine levels. For example, if you want to log *error*, *warning* and *info* levels, set the `XCDYouTubeKitLogLevel` environment variable to `0x7` (0x01 | 0x02 | 0x04).
*
* If you do not set the `XCDYouTubeKitLogLevel` environment variable, only warning and error levels are logged.
*
* [1]: https://github.com/CocoaLumberjack/CocoaLumberjack/blob/master/Documentation/CustomContext.md
*/
@interface XCDYouTubeLogger : NSObject
/**
* -------------------
* @name Custom Logger
* -------------------
*/
/**
* If you prefer not to use CocoaLumberjack and want something more advanced than the default `NSLog` implementation, you can use this method to write your own logger.
*
* @param logHandler The block called when a log is emitted by the XCDYouTubeKit framework. If you set the log handler to nil, logging will be completely disabled.
*
* @discussion Here is a description of the log handler parameters.
*
* - The `message` parameter is a block returning a string that you must call to evaluate the log message.
* - The `level` parameter is the log level of the message, see `<XCDLogLevel>`.
* - The `file` parameter is the full path of the file, captured with the `__FILE__` macro where the log is emitted.
* - The `function` parameter is the function name, captured with the `__PRETTY_FUNCTION__` macro where the log is emitted.
* - The `line` parameter is the line number, captured with the `__LINE__` macro where the log is emitted.
*
* Here is how you could implement a custom log handler with [NSLogger](https://github.com/fpillet/NSLogger):
*
* ```
* [XCDYouTubeLogger setLogHandler:^(NSString * (^message)(void), XCDLogLevel level, const char *file, const char *function, NSUInteger line) {
* LogMessageRawF(file, (int)line, function, @"XCDYouTubeKit", (int)level, message());
* }];
* ```
*/
+ (void) setLogHandler:(void (^)(NSString * (^message)(void), XCDLogLevel level, const char *file, const char *function, NSUInteger line))logHandler;
@end

View file

@ -0,0 +1,63 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import "XCDYouTubeLogger.h"
#import <objc/runtime.h>
const NSInteger XCDYouTubeKitLumberjackContext = (NSInteger)0xced70676;
@protocol XCDYouTubeLogger_DDLog
// Copied from CocoaLumberjack's DDLog interface
+ (void) log:(BOOL)asynchronous message:(NSString *)message level:(NSUInteger)level flag:(NSUInteger)flag context:(NSInteger)context file:(const char *)file function:(const char *)function line:(NSUInteger)line tag:(id)tag;
@end
static Class DDLogClass = Nil;
static void (^const CocoaLumberjackLogHandler)(NSString * (^)(void), XCDLogLevel, const char *, const char *, NSUInteger) = ^(NSString *(^message)(void), XCDLogLevel level, const char *file, const char *function, NSUInteger line)
{
// The `XCDLogLevel` enum was carefully crafted to match the `DDLogFlag` options from DDLog.h
[DDLogClass log:YES message:message() level:NSUIntegerMax flag:(1 << level) context:XCDYouTubeKitLumberjackContext file:file function:function line:line tag:nil];
};
static void (^LogHandler)(NSString * (^)(void), XCDLogLevel, const char *, const char *, NSUInteger) = ^(NSString *(^message)(void), XCDLogLevel level, const char *file, const char *function, NSUInteger line)
{
char *logLevelString = getenv("XCDYouTubeKitLogLevel");
NSUInteger logLevelMask = logLevelString ? strtoul(logLevelString, NULL, 0) : (1 << XCDLogLevelError) | (1 << XCDLogLevelWarning);
if ((1 << level) & logLevelMask)
NSLog(@"[XCDYouTubeKit] %@", message());
};
@implementation XCDYouTubeLogger
+ (void) initialize
{
static dispatch_once_t once;
dispatch_once(&once, ^{
DDLogClass = objc_lookUpClass("DDLog");
if (DDLogClass)
{
const SEL logSeletor = @selector(log:message:level:flag:context:file:function:line:tag:);
const char *typeEncoding = method_getTypeEncoding((Method)class_getClassMethod(DDLogClass, logSeletor));
const char *expectedTypeEncoding = protocol_getMethodDescription(@protocol(XCDYouTubeLogger_DDLog), logSeletor, /* isRequiredMethod: */ YES, /* isInstanceMethod: */ NO).types;
if (typeEncoding && expectedTypeEncoding && strcmp(typeEncoding, expectedTypeEncoding) == 0)
LogHandler = CocoaLumberjackLogHandler;
else
NSLog(@"[XCDYouTubeKit] Incompatible CocoaLumberjack version. Expected \"%@\", got \"%@\".", expectedTypeEncoding ? @(expectedTypeEncoding) : @"", typeEncoding ? @(typeEncoding) : @"");
}
});
}
+ (void) setLogHandler:(void (^)(NSString * (^message)(void), XCDLogLevel level, const char *file, const char *function, NSUInteger line))logHandler
{
LogHandler = logHandler;
}
+ (void) logMessage:(NSString * (^)(void))message level:(XCDLogLevel)level file:(const char *)file function:(const char *)function line:(NSUInteger)line
{
if (LogHandler)
LogHandler(message, level, file, function, line);
}
@end

View file

@ -0,0 +1,23 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* The `XCDYouTubeOperation` protocol is adopted by opaque objects returned by the `<-[XCDYouTubeClient getVideoWithIdentifier:completionHandler:]>` method.
*/
@protocol XCDYouTubeOperation <NSObject>
/**
* ---------------
* @name Canceling
* ---------------
*/
/**
* Cancels the operation. If the operation is already finished, does nothing.
*/
- (void) cancel;
@end

View file

@ -0,0 +1,14 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import <Foundation/Foundation.h>
__attribute__((visibility("hidden")))
@interface XCDYouTubePlayerScript : NSObject
- (instancetype) initWithString:(NSString *)string;
- (NSString *) unscrambleSignature:(NSString *)scrambledSignature;
@end

View file

@ -0,0 +1,125 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import "XCDYouTubePlayerScript.h"
#import <JavaScriptCore/JavaScriptCore.h>
#import "XCDYouTubeLogger+Private.h"
@interface XCDYouTubePlayerScript ()
@property (nonatomic, strong) JSContext *context;
@property (nonatomic, strong) JSValue *signatureFunction;
@end
@implementation XCDYouTubePlayerScript
- (instancetype) initWithString:(NSString *)string
{
if (!(self = [super init]))
return nil; // LCOV_EXCL_LINE
_context = [JSContext new];
_context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
XCDYouTubeLogWarning(@"JavaScript exception: %@", exception);
};
NSDictionary *environment = @{
@"document": @{
@"documentElement": @{}
},
@"location": @{
@"hash": @""
},
@"navigator": @{
@"userAgent": @""
},
};
_context[@"window"] = @{};
for (NSString *propertyName in environment)
{
JSValue *value = [JSValue valueWithObject:environment[propertyName] inContext:_context];
_context[propertyName] = value;
_context[@"window"][propertyName] = value;
}
NSString *matchMediaJsFunction = @"var matchMediaWindow=this;matchMediaWindow.matchMedia=function(a){return false;};";
NSString *script = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
script=[matchMediaJsFunction stringByAppendingString:(script)];
[_context evaluateScript:script];
NSRegularExpression *anonymousFunctionRegularExpression = [NSRegularExpression regularExpressionWithPattern:@"\\(function\\(([^)]*)\\)\\{(.*)\\}\\)\\(([^)]*)\\)" options:NSRegularExpressionDotMatchesLineSeparators error:NULL];
NSTextCheckingResult *anonymousFunctionResult = [anonymousFunctionRegularExpression firstMatchInString:script options:(NSMatchingOptions)0 range:NSMakeRange(0, script.length)];
if (anonymousFunctionResult.numberOfRanges > 3)
{
NSArray *parameters = [[script substringWithRange:[anonymousFunctionResult rangeAtIndex:1]] componentsSeparatedByString:@","];
NSArray *arguments = [[script substringWithRange:[anonymousFunctionResult rangeAtIndex:3]] componentsSeparatedByString:@","];
if (parameters.count == arguments.count)
{
for (NSUInteger i = 0; i < parameters.count; i++)
{
_context[parameters[i]] = _context[arguments[i]];
}
}
NSString *anonymousFunctionBody = [script substringWithRange:[anonymousFunctionResult rangeAtIndex:2]];
[_context evaluateScript:anonymousFunctionBody];
}
else
{
XCDYouTubeLogWarning(@"Unexpected player script (no anonymous function found)");
}
//See list of regex patterns here https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/youtube.py#L1179
NSArray<NSString *>*patterns = @[@"\\.sig\\|\\|([a-zA-Z0-9$]+)\\(",
@"[\"']signature[\"']\\s*,\\s*([^\\(]+)",
@"yt\\.akamaized\\.net/\\)\\s*\\|\\|\\s*.*?\\s*c\\s*&&d.set\\([^,]+\\s*,\\s*([a-zA-Z0-9$]+)",
@"\\bcs*&&\\s*d\\.set\\([^,]+\\s*,\\s*([a-zA-Z0-9$]+)\\C",
@"\\bc\\s*&&\\s*d\\.set\\([^,]+\\s*,\\s*\\([^)]*\\)\\s*\\(\\s*([a-zA-Z0-9$]+)\\("
];
NSMutableArray<NSRegularExpression *>*validRegularExpressions = [NSMutableArray new];
for (NSString *pattern in patterns) {
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:NULL];
if (regex != nil)
{
[validRegularExpressions addObject:regex];
}
}
for (NSRegularExpression *regularExpression in validRegularExpressions) {
NSArray<NSTextCheckingResult *> *regexResults = [regularExpression matchesInString:script options:(NSMatchingOptions)0 range:NSMakeRange(0, script.length)];
for (NSTextCheckingResult *signatureResult in regexResults)
{
NSString *signatureFunctionName = signatureResult.numberOfRanges > 1 ? [script substringWithRange:[signatureResult rangeAtIndex:1]] : nil;
if (!signatureFunctionName)
continue;
JSValue *signatureFunction = self.context[signatureFunctionName];
if (signatureFunction.isObject)
{
_signatureFunction = signatureFunction;
break;
}
}
}
if (!_signatureFunction)
XCDYouTubeLogWarning(@"No signature function in player script");
return self;
}
- (NSString *) unscrambleSignature:(NSString *)scrambledSignature
{
if (!self.signatureFunction || !scrambledSignature)
return nil;
JSValue *unscrambledSignature = [self.signatureFunction callWithArguments:@[ scrambledSignature ]];
return [unscrambledSignature isString] ? [unscrambledSignature toString] : nil;
}
@end

View file

@ -0,0 +1,24 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import <XCDYouTubeKit/XCDYouTubeVideo.h>
#import "XCDYouTubePlayerScript.h"
#define XCDYouTubeErrorUseCipherSignature -1000
extern NSString *const XCDYouTubeNoStreamVideoUserInfoKey;
extern NSDictionary *XCDDictionaryWithQueryString(NSString *string);
extern NSString *XCDQueryStringWithDictionary(NSDictionary *dictionary);
extern NSArray *XCDCaptionArrayWithString(NSString *string);
@interface XCDYouTubeVideo ()
- (instancetype) initWithIdentifier:(NSString *)identifier info:(NSDictionary *)info playerScript:(XCDYouTubePlayerScript *)playerScript response:(NSURLResponse *)response error:(NSError **)error;
- (void) mergeVideo:(XCDYouTubeVideo *)video;
- (void) mergeDashManifestStreamURLs:(NSDictionary *)dashManifestStreamURLs;
@end

View file

@ -0,0 +1,147 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#if !__has_feature(nullability)
#define NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_END
#define nullable
#endif
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The quality of YouTube videos. These values are used as keys in the `<[XCDYouTubeVideo streamURLs]>` property.
*
* The constant numbers are the YouTube [itag](https://en.wikipedia.org/wiki/Talk:YouTube/Archive_22#Moved_from_YouTube#Quality_formats) values.
*/
typedef NS_ENUM(NSUInteger, XCDYouTubeVideoQuality) {
/**
* Video: 240p MPEG-4 Visual | 0.175 Mbit/s
* Audio: AAC | 36 kbit/s
*/
XCDYouTubeVideoQualitySmall240 = 36,
/**
* Video: 360p H.264 | 0.5 Mbit/s
* Audio: AAC | 96 kbit/s
*/
XCDYouTubeVideoQualityMedium360 = 18,
/**
* Video: 720p H.264 | 2-3 Mbit/s
* Audio: AAC | 192 kbit/s
*/
XCDYouTubeVideoQualityHD720 = 22,
/**
* Video: 1080p H.264 | 35.9 Mbit/s
* Audio: AAC | 192 kbit/s
*
* @deprecated YouTube has removed 1080p mp4 videos.
*/
XCDYouTubeVideoQualityHD1080 DEPRECATED_MSG_ATTRIBUTE("YouTube has removed 1080p mp4 videos.") = 37,
};
/**
* Used as a key in the `streamURLs` property of the `XCDYouTubeVideo` class for live videos.
*/
extern NSString *const XCDYouTubeVideoQualityHTTPLiveStreaming;
/**
* Represents a YouTube video. Use the `<-[XCDYouTubeClient getVideoWithIdentifier:completionHandler:]>` method to obtain a `XCDYouTubeVideo` object.
*/
@interface XCDYouTubeVideo : NSObject <NSCopying>
/**
* --------------------------------
* @name Accessing video properties
* --------------------------------
*/
/**
* The 11 characters YouTube video identifier.
*/
@property (nonatomic, readonly) NSString *identifier;
/**
* The title of the video.
*/
@property (nonatomic, readonly) NSString *title;
/**
* The duration of the video in seconds.
*/
@property (nonatomic, readonly) NSTimeInterval duration;
/**
* A thumbnail URL for an image of small size, i.e. 120×90. May be nil.
*/
@property (nonatomic, readonly, nullable) NSURL *thumbnailURL;
/**
* A thumbnail URL for an image of small size, i.e. 120×90. May be nil.
*/
@property (nonatomic, readonly, nullable) NSURL *smallThumbnailURL DEPRECATED_MSG_ATTRIBUTE("Renamed. Use `thumbnailURL` instead.");
/**
* A thumbnail URL for an image of medium size, i.e. 320×180, 480×360 or 640×480. May be nil.
*/
@property (nonatomic, readonly, nullable) NSURL *mediumThumbnailURL DEPRECATED_MSG_ATTRIBUTE("No longer available. Use `thumbnailURL` instead.");
/**
* A thumbnail URL for an image of large size, i.e. 1'280×720 or 1'980×1'080. May be nil.
*/
@property (nonatomic, readonly, nullable) NSURL *largeThumbnailURL DEPRECATED_MSG_ATTRIBUTE("No longer available. Use `thumbnailURL` instead.");
/**
* A dictionary of video stream URLs.
*
* The keys are the YouTube [itag](https://en.wikipedia.org/wiki/YouTube#Quality_and_formats) values as `NSNumber` objects. The values are the video URLs as `NSURL` objects. There is also the special `XCDYouTubeVideoQualityHTTPLiveStreaming` key for live videos.
*
* You should not store the URLs for later use since they have a limited lifetime and are bound to an IP address.
*
* @see XCDYouTubeVideoQuality
* @see expirationDate
*/
#if __has_feature(objc_generics)
@property (nonatomic, readonly) NSDictionary<id, NSURL *> *streamURLs;
#else
@property (nonatomic, readonly) NSDictionary *streamURLs;
#endif
/**
* A dictionary of caption URLs (XML).
*
* The keys are the [language codes](https://www.loc.gov/standards/iso639-2/php/code_list.php) values as `NSString` objects. The values are the caption URLs as `NSURL` objects.
*
* You should not store the URLs for later use since they have a limited lifetime.
*/
#if __has_feature(objc_generics)
@property (nonatomic, readonly, nullable) NSDictionary<id, NSURL *> *captionURLs;
#else
@property (nonatomic, readonly, nullable) NSDictionary *captionURLs;
#endif
/**
* A dictionary of auto generated caption URLs (XML).
*
* The keys are the [language codes](https://www.loc.gov/standards/iso639-2/php/code_list.php) values as `NSString` objects. The values are the caption URLs as `NSURL` objects. These URLs are the ones that were automatically generated by YouTube.
*
* You should not store the URLs for later use since they have a limited lifetime.
*/
#if __has_feature(objc_generics)
@property (nonatomic, readonly, nullable) NSDictionary<id, NSURL *> *autoGeneratedCaptionURLs;
#else
@property (nonatomic, readonly, nullable) NSDictionary *autoGeneratedCaptionURLs;
#endif
/**
* The expiration date of the video.
*
* After this date, the stream URLs will not be playable. May be nil if it can not be determined, for example in live videos.
*/
@property (nonatomic, readonly, nullable) NSDate *expirationDate;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,319 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import "XCDYouTubeVideo+Private.h"
#import "XCDYouTubeError.h"
#import "XCDYouTubeLogger+Private.h"
#import <objc/runtime.h>
NSString *const XCDYouTubeVideoErrorDomain = @"XCDYouTubeVideoErrorDomain";
NSString *const XCDYouTubeAllowedCountriesUserInfoKey = @"AllowedCountries";
NSString *const XCDYouTubeNoStreamVideoUserInfoKey = @"NoStreamVideo";
NSString *const XCDYouTubeVideoQualityHTTPLiveStreaming = @"HTTPLiveStreaming";
NSArray <NSDictionary *> *XCDCaptionArrayWithString(NSString *string)
{
NSError *error = nil;
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
if (!data) { return nil; }
NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
if (error) { return nil; }
NSDictionary *captions = JSON[@"captions"];
NSDictionary *playerCaptionsTracklistRenderer = captions[@"playerCaptionsTracklistRenderer"];
NSArray *captionTracks = playerCaptionsTracklistRenderer[@"captionTracks"];
if (captionTracks.count == 0 || captionTracks == nil) { return nil; }
return captionTracks;
}
NSDictionary *XCDDictionaryWithQueryString(NSString *string)
{
NSMutableDictionary *dictionary = [NSMutableDictionary new];
NSArray *fields = [string componentsSeparatedByString:@"&"];
for (NSString *field in fields)
{
NSArray *pair = [field componentsSeparatedByString:@"="];
if (pair.count == 2)
{
NSString *key = pair[0];
NSString *value = [(NSString *)pair[1] stringByRemovingPercentEncoding];
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
if (dictionary[key] && ![(NSObject *)dictionary[key] isEqual:value])
{
XCDYouTubeLogWarning(@"Using XCDDictionaryWithQueryString is inappropriate because the query string has multiple values for the key '%@'\n"
@"Query: %@\n"
@"Discarded value: %@", key, string, dictionary[key]);
}
dictionary[key] = value;
}
}
return [dictionary copy];
}
NSString *XCDQueryStringWithDictionary(NSDictionary *dictionary)
{
NSArray *keys = [dictionary.allKeys filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return [(NSObject *)evaluatedObject isKindOfClass:[NSString class]];
}]];
NSMutableString *query = [NSMutableString new];
for (NSString *key in [keys sortedArrayUsingSelector:@selector(compare:)])
{
if (query.length > 0)
[query appendString:@"&"];
[query appendFormat:@"%@=%@", key, [(NSObject *)dictionary[key] description]];
}
return [query stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
}
static NSString *SortedDictionaryDescription(NSDictionary *dictionary)
{
NSArray *sortedKeys = [dictionary.allKeys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [[(NSObject *)obj1 description] compare:[(NSObject *) obj2 description] options:NSNumericSearch];
}];
NSMutableString *description = [[NSMutableString alloc] initWithString:@"{\n"];
for (id key in sortedKeys)
{
[description appendFormat:@"\t%@ \u2192 %@\n", key, dictionary[key]];
}
[description appendString:@"}"];
return [description copy];
}
static NSURL * URLBySettingParameter(NSURL *URL, NSString *key, NSString *percentEncodedValue)
{
NSString *pattern = [NSString stringWithFormat:@"((?:^|&)%@=)[^&]*", key];
NSString *template = [NSString stringWithFormat:@"$1%@", percentEncodedValue];
NSURLComponents *components = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO];
NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:pattern options:(NSRegularExpressionOptions)0 error:NULL];
NSMutableString *percentEncodedQuery = [components.percentEncodedQuery ?: @"" mutableCopy];
NSUInteger numberOfMatches = [regularExpression replaceMatchesInString:percentEncodedQuery options:(NSMatchingOptions)0 range:NSMakeRange(0, percentEncodedQuery.length) withTemplate:template];
if (numberOfMatches == 0)
[percentEncodedQuery appendFormat:@"%@%@=%@", percentEncodedQuery.length > 0 ? @"&" : @"", key, percentEncodedValue];
components.percentEncodedQuery = percentEncodedQuery;
return components.URL;
}
@implementation XCDYouTubeVideo
static NSDate * ExpirationDate(NSURL *streamURL)
{
NSDictionary *query = XCDDictionaryWithQueryString(streamURL.query);
NSTimeInterval expire = [(NSString *)query[@"expire"] doubleValue];
return expire > 0 ? [NSDate dateWithTimeIntervalSince1970:expire] : nil;
}
- (instancetype) initWithIdentifier:(NSString *)identifier info:(NSDictionary *)info playerScript:(XCDYouTubePlayerScript *)playerScript response:(NSURLResponse *)response error:(NSError * __autoreleasing *)error
{
if (!(self = [super init]))
return nil; // LCOV_EXCL_LINE
_identifier = identifier;
NSString *streamMap = info[@"url_encoded_fmt_stream_map"];
NSString *captionsMap = info[@"player_response"];
NSString *httpLiveStream = info[@"hlsvp"];
NSString *adaptiveFormats = info[@"adaptive_fmts"];
NSMutableDictionary *userInfo = response.URL ? [@{ NSURLErrorKey: (id)response.URL } mutableCopy] : [NSMutableDictionary new];
if (streamMap.length > 0 || httpLiveStream.length > 0)
{
NSMutableArray *streamQueries = [[streamMap componentsSeparatedByString:@","] mutableCopy];
[streamQueries addObjectsFromArray:[adaptiveFormats componentsSeparatedByString:@","]];
NSString *title = info[@"title"] ?: @"";
_title = title;
_duration = [(NSString *)info[@"length_seconds"] doubleValue];
NSString *thumbnail = info[@"thumbnail_url"] ?: info[@"iurl"];
_thumbnailURL = thumbnail ? [NSURL URLWithString:thumbnail] : nil;
NSMutableDictionary *streamURLs = [NSMutableDictionary new];
if (httpLiveStream)
streamURLs[XCDYouTubeVideoQualityHTTPLiveStreaming] = [NSURL URLWithString:httpLiveStream];
NSMutableDictionary *captionURLs = [NSMutableDictionary new];
NSMutableDictionary *autoGeneratedCaptionURLs = [NSMutableDictionary new];
for (NSDictionary *caption in XCDCaptionArrayWithString(captionsMap))
{
NSString *languageCode = caption[@"languageCode"];
NSString *captionVersion = caption[@"vssId"];
NSString *captionURLString = caption[@"baseUrl"];
if (!captionURLString)
{
continue;
}
NSURL *captionURL = [NSURL URLWithString:captionURLString];
if (captionURL && languageCode)
{
if ([languageCode isEqualToString:@"und"])
{
//Skip because this is a special code than is used to indicate that the lanauage code is undetermined.
continue;
}
if([captionVersion hasPrefix:@"a"])
{
//Indicates the caption was auto generated
autoGeneratedCaptionURLs[languageCode] = captionURL;
} else
{
captionURLs[languageCode] = captionURL;
}
}
}
if (captionURLs.count > 0)
{
_captionURLs = [captionURLs copy];
}
if (autoGeneratedCaptionURLs.count > 0)
{
_autoGeneratedCaptionURLs = [autoGeneratedCaptionURLs copy];
}
for (NSString *streamQuery in streamQueries)
{
NSDictionary *stream = XCDDictionaryWithQueryString(streamQuery);
NSString *scrambledSignature = stream[@"s"];
if (scrambledSignature && !playerScript)
{
userInfo[XCDYouTubeNoStreamVideoUserInfoKey] = self;
if (error)
*error = [NSError errorWithDomain:XCDYouTubeVideoErrorDomain code:XCDYouTubeErrorUseCipherSignature userInfo:userInfo];
return nil;
}
NSString *signature = [playerScript unscrambleSignature:scrambledSignature];
if (playerScript && scrambledSignature && !signature)
continue;
NSString *urlString = stream[@"url"];
NSString *itag = stream[@"itag"];
if (urlString && itag)
{
NSURL *streamURL = [NSURL URLWithString:urlString];
if (!_expirationDate)
_expirationDate = ExpirationDate(streamURL);
if (signature)
{
NSString *escapedSignature = [signature stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
streamURL = URLBySettingParameter(streamURL, @"signature", escapedSignature);
}
streamURLs[@(itag.integerValue)] = URLBySettingParameter(streamURL, @"ratebypass", @"yes");
}
}
_streamURLs = [streamURLs copy];
if (_streamURLs.count == 0)
{
if (error)
*error = [NSError errorWithDomain:XCDYouTubeVideoErrorDomain code:XCDYouTubeErrorNoStreamAvailable userInfo:userInfo];
return nil;
}
return self;
}
else
{
if (error)
{
NSString *reason = info[@"reason"];
if (reason)
{
reason = [reason stringByReplacingOccurrencesOfString:@"<br\\s*/?>" withString:@" " options:NSRegularExpressionSearch range:NSMakeRange(0, reason.length)];
reason = [reason stringByReplacingOccurrencesOfString:@"\n" withString:@" " options:(NSStringCompareOptions)0 range:NSMakeRange(0, reason.length)];
NSRange range;
while ((range = [reason rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound)
reason = [reason stringByReplacingCharactersInRange:range withString:@""];
userInfo[NSLocalizedDescriptionKey] = reason;
}
NSString *errorcode = info[@"errorcode"];
NSInteger code = errorcode ? errorcode.integerValue : XCDYouTubeErrorNoStreamAvailable;
*error = [NSError errorWithDomain:XCDYouTubeVideoErrorDomain code:code userInfo:userInfo];
}
return nil;
}
}
- (void) mergeVideo:(XCDYouTubeVideo *)video
{
unsigned int count;
objc_property_t *properties = class_copyPropertyList(self.class, &count);
for (unsigned int i = 0; i < count; i++)
{
NSString *propertyName = @(property_getName(properties[i]));
if (![self valueForKey:propertyName])
[self setValue:[video valueForKey:propertyName] forKeyPath:propertyName];
}
free(properties);
}
- (void) mergeDashManifestStreamURLs:(NSDictionary *)dashManifestStreamURLs {
NSMutableDictionary *approvedStreams = [NSMutableDictionary new];
for (NSString *itag in dashManifestStreamURLs) {
if (self.streamURLs[itag] == nil)
approvedStreams[itag] = dashManifestStreamURLs[itag];
}
NSMutableDictionary *newStreams = [NSMutableDictionary dictionaryWithDictionary:self.streamURLs];
[newStreams addEntriesFromDictionary:approvedStreams];
[self setValue:newStreams.copy forKeyPath:NSStringFromSelector(@selector(streamURLs))];
}
#pragma mark - NSObject
- (BOOL) isEqual:(id)object
{
return [(NSObject *)object isKindOfClass:[XCDYouTubeVideo class]] && [((XCDYouTubeVideo *)object).identifier isEqual:self.identifier];
}
- (NSUInteger) hash
{
return self.identifier.hash;
}
- (NSString *) description
{
return [NSString stringWithFormat:@"[%@] %@", self.identifier, self.title];
}
- (NSString *) debugDescription
{
NSDateComponentsFormatter *dateComponentsFormatter = [NSDateComponentsFormatter new];
dateComponentsFormatter.unitsStyle = NSDateComponentsFormatterUnitsStyleAbbreviated;
NSString *duration = [dateComponentsFormatter stringFromTimeInterval:self.duration] ?: [NSString stringWithFormat:@"%@ seconds", @(self.duration)];
NSString *thumbnailDescription = [NSString stringWithFormat:@"Thumbnail: %@", self.thumbnailURL];
NSString *streamsDescription = SortedDictionaryDescription(self.streamURLs);
return [NSString stringWithFormat:@"<%@: %p> %@\nDuration: %@\nExpiration date: %@\n%@\nStreams: %@", self.class, self, self.description, duration, self.expirationDate, thumbnailDescription, streamsDescription];
}
#pragma mark - NSCopying
- (id) copyWithZone:(NSZone *)zone
{
return self;
}
@end

View file

@ -0,0 +1,74 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#if !__has_feature(nullability)
#define NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_END
#define nullable
#endif
#import <Foundation/Foundation.h>
#import <XCDYouTubeKit/XCDYouTubeOperation.h>
#import <XCDYouTubeKit/XCDYouTubeVideo.h>
NS_ASSUME_NONNULL_BEGIN
/**
* XCDYouTubeVideoOperation is a subclass of `NSOperation` that connects to the YouTube API and parse the response.
*
* You should probably use the higher level class `<XCDYouTubeClient>`. Use this class only if you are very familiar with `NSOperation` and need to manage dependencies between operations.
*/
@interface XCDYouTubeVideoOperation : NSOperation <XCDYouTubeOperation>
/**
* ------------------
* @name Initializing
* ------------------
*/
/**
* Initializes a video operation with the specified video identifier and language identifier.
*
* @param videoIdentifier A 11 characters YouTube video identifier.
* @param languageIdentifier An [ISO 639-1 two-letter language code](http://www.loc.gov/standards/iso639-2/php/code_list.php) used for error localization. If you pass a nil language identifier then English (`en`) is used.
*
* @return An initialized `XCDYouTubeVideoOperation` object.
*/
- (instancetype) initWithVideoIdentifier:(NSString *)videoIdentifier languageIdentifier:(nullable NSString *)languageIdentifier;
/**
Initializes a video operation with the specified video identifier and language identifier and cookies.
@param videoIdentifier A 11 characters YouTube video identifier.
@param languageIdentifier An [ISO 639-1 two-letter language code](http://www.loc.gov/standards/iso639-2/php/code_list.php) used for error localization. If you pass a nil language identifier then English (`en`) is used.
@param cookies An array of `NSHTTPCookie` objects, can be nil. These cookies can be used for certain videos that require a login.
@return An initialized `XCDYouTubeVideoOperation` object.
*/
- (instancetype) initWithVideoIdentifier:(NSString *)videoIdentifier languageIdentifier:(nullable NSString *)languageIdentifier cookies:(nullable NSArray <NSHTTPCookie *>*)cookies NS_DESIGNATED_INITIALIZER;
/**
* --------------------------------
* @name Accessing operation result
* --------------------------------
*/
/**
* Returns an error of the `XCDYouTubeVideoErrorDomain` domain if the operation failed or nil if it succeeded.
*
* Returns nil if the operation is not yet finished or if it was canceled.
*/
@property (atomic, readonly, nullable) NSError *error;
/**
* Returns a video object if the operation succeeded or nil if it failed.
*
* Returns nil if the operation is not yet finished or if it was canceled.
*/
@property (atomic, readonly, nullable) XCDYouTubeVideo *video;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,410 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import "XCDYouTubeVideoOperation.h"
#import <objc/runtime.h>
#import "XCDYouTubeVideo+Private.h"
#import "XCDYouTubeError.h"
#import "XCDYouTubeVideoWebpage.h"
#import "XCDYouTubeDashManifestXML.h"
#import "XCDYouTubePlayerScript.h"
#import "XCDYouTubeLogger+Private.h"
typedef NS_ENUM(NSUInteger, XCDYouTubeRequestType) {
XCDYouTubeRequestTypeGetVideoInfo = 1,
XCDYouTubeRequestTypeWatchPage,
XCDYouTubeRequestTypeEmbedPage,
XCDYouTubeRequestTypeJavaScriptPlayer,
XCDYouTubeRequestTypeDashManifest,
};
@interface XCDYouTubeVideoOperation ()
@property (atomic, copy, readonly) NSString *videoIdentifier;
@property (atomic, copy, readonly) NSString *languageIdentifier;
@property (atomic, strong, readonly) NSArray <NSHTTPCookie *> *cookies;
@property (atomic, assign) NSInteger requestCount;
@property (atomic, assign) XCDYouTubeRequestType requestType;
@property (atomic, strong) NSMutableArray *eventLabels;
@property (atomic, strong) XCDYouTubeVideo *lastSuccessfulVideo;
@property (atomic, readonly) NSURLSession *session;
@property (atomic, strong) NSURLSessionDataTask *dataTask;
@property (atomic, assign) BOOL isExecuting;
@property (atomic, assign) BOOL isFinished;
@property (atomic, readonly) dispatch_semaphore_t operationStartSemaphore;
@property (atomic, strong) XCDYouTubeVideoWebpage *webpage;
@property (atomic, strong) XCDYouTubeVideoWebpage *embedWebpage;
@property (atomic, strong) XCDYouTubePlayerScript *playerScript;
@property (atomic, strong) XCDYouTubeVideo *noStreamVideo;
@property (atomic, strong) NSError *lastError;
@property (atomic, strong) NSError *youTubeError; // Error actually coming from the YouTube API, i.e. explicit and localized error
@property (atomic, strong, readwrite) NSError *error;
@property (atomic, strong, readwrite) XCDYouTubeVideo *video;
@end
@implementation XCDYouTubeVideoOperation
static NSError *YouTubeError(NSError *error, NSSet *regionsAllowed, NSString *languageIdentifier)
{
if (error.code == XCDYouTubeErrorRestrictedPlayback && regionsAllowed.count > 0)
{
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:languageIdentifier];
NSMutableSet *allowedCountries = [NSMutableSet new];
for (NSString *countryCode in regionsAllowed)
{
NSString *country = [locale displayNameForKey:NSLocaleCountryCode value:countryCode];
[allowedCountries addObject:country ?: countryCode];
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo];
userInfo[XCDYouTubeAllowedCountriesUserInfoKey] = [allowedCountries copy];
return [NSError errorWithDomain:error.domain code:error.code userInfo:[userInfo copy]];
}
else
{
return error;
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
- (instancetype) init
{
@throw [NSException exceptionWithName:NSGenericException reason:@"Use the `initWithVideoIdentifier:cookies:languageIdentifier:` method instead." userInfo:nil];
} // LCOV_EXCL_LINE
#pragma clang diagnostic pop
- (instancetype) initWithVideoIdentifier:(NSString *)videoIdentifier languageIdentifier:(NSString *)languageIdentifier cookies:(NSArray<NSHTTPCookie *> *)cookies
{
if (!(self = [super init]))
return nil; // LCOV_EXCL_LINE
_videoIdentifier = videoIdentifier ?: @"";
_languageIdentifier = languageIdentifier ?: @"en";
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]];
_cookies = [cookies copy];
for (NSHTTPCookie *cookie in _cookies) {
[_session.configuration.HTTPCookieStorage setCookie:cookie];
}
_operationStartSemaphore = dispatch_semaphore_create(0);
return self;
}
- (instancetype) initWithVideoIdentifier:(NSString *)videoIdentifier languageIdentifier:(NSString *)languageIdentifier
{
return [self initWithVideoIdentifier:videoIdentifier languageIdentifier:languageIdentifier cookies:nil];
}
#pragma mark - Requests
- (void) startNextRequest
{
if (self.eventLabels.count == 0)
{
if (self.requestType == XCDYouTubeRequestTypeWatchPage || self.webpage)
[self finishWithError];
else
[self startWatchPageRequest];
}
else
{
NSString *eventLabel = [self.eventLabels objectAtIndex:0];
[self.eventLabels removeObjectAtIndex:0];
NSDictionary *query = @{ @"video_id": self.videoIdentifier, @"hl": self.languageIdentifier, @"el": eventLabel, @"ps": @"default" };
NSString *queryString = XCDQueryStringWithDictionary(query);
NSURL *videoInfoURL = [NSURL URLWithString:[@"https://www.youtube.com/get_video_info?" stringByAppendingString:queryString]];
[self startRequestWithURL:videoInfoURL type:XCDYouTubeRequestTypeGetVideoInfo];
}
}
- (void) startWatchPageRequest
{
NSDictionary *query = @{ @"v": self.videoIdentifier, @"hl": self.languageIdentifier, @"has_verified": @YES, @"bpctr": @9999999999 };
NSString *queryString = XCDQueryStringWithDictionary(query);
NSURL *webpageURL = [NSURL URLWithString:[@"https://www.youtube.com/watch?" stringByAppendingString:queryString]];
[self startRequestWithURL:webpageURL type:XCDYouTubeRequestTypeWatchPage];
}
- (void) startRequestWithURL:(NSURL *)url type:(XCDYouTubeRequestType)requestType
{
if (self.isCancelled)
return;
// Max (age-restricted VEVO) = 2×GetVideoInfo + 1×WatchPage + 1×EmbedPage + 1×JavaScriptPlayer + 1×GetVideoInfo + 1xDashManifest
if (++self.requestCount > 7)
{
// This condition should never happen but the request flow is quite complex so better abort here than go into an infinite loop of requests
[self finishWithError];
return;
}
XCDYouTubeLogDebug(@"Starting request: %@", url);
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
[request setValue:self.languageIdentifier forHTTPHeaderField:@"Accept-Language"];
[request setValue:[NSString stringWithFormat:@"https://youtube.com/watch?v=%@", self.videoIdentifier] forHTTPHeaderField:@"Referer"];
self.dataTask = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
if (self.isCancelled)
return;
if (error)
[self handleConnectionError:error requestType:requestType];
else
[self handleConnectionSuccessWithData:data response:response requestType:requestType];
}];
[self.dataTask resume];
self.requestType = requestType;
}
#pragma mark - Response Dispatch
- (void) handleConnectionSuccessWithData:(NSData *)data response:(NSURLResponse *)response requestType:(XCDYouTubeRequestType)requestType
{
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((__bridge CFStringRef)response.textEncodingName ?: CFSTR(""));
// Use kCFStringEncodingMacRoman as fallback because it defines characters for every byte value and is ASCII compatible. See https://mikeash.com/pyblog/friday-qa-2010-02-19-character-encodings.html
NSString *responseString = CFBridgingRelease(CFStringCreateWithBytes(kCFAllocatorDefault, data.bytes, (CFIndex)data.length, encoding != kCFStringEncodingInvalidId ? encoding : kCFStringEncodingMacRoman, false)) ?: @"";
NSAssert(responseString.length > 0, @"Failed to decode response from %@ (response.textEncodingName = %@, data.length = %@)", response.URL, response.textEncodingName, @(data.length));
XCDYouTubeLogVerbose(@"Response: %@\n%@", response, responseString);
switch (requestType)
{
case XCDYouTubeRequestTypeGetVideoInfo:
[self handleVideoInfoResponseWithInfo:XCDDictionaryWithQueryString(responseString) response:response];
break;
case XCDYouTubeRequestTypeWatchPage:
[self handleWebPageWithHTMLString:responseString];
break;
case XCDYouTubeRequestTypeEmbedPage:
[self handleEmbedWebPageWithHTMLString:responseString];
break;
case XCDYouTubeRequestTypeJavaScriptPlayer:
[self handleJavaScriptPlayerWithScript:responseString];
break;
case XCDYouTubeRequestTypeDashManifest:
[self handleDashManifestWithXMLString:responseString response:response];
break;
}
}
- (void) handleConnectionError:(NSError *)connectionError requestType:(XCDYouTubeRequestType)requestType
{
//Shoud not return a connection error if was as a result of requesting the Dash Manifiest (we have a sucessfully created `XCDYouTubeVideo` and should just finish the operation as if were a 'sucessful' one
if (requestType == XCDYouTubeRequestTypeDashManifest)
{
[self finishWithVideo:self.lastSuccessfulVideo];
return;
}
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: connectionError.localizedDescription,
NSUnderlyingErrorKey: connectionError };
self.lastError = [NSError errorWithDomain:XCDYouTubeVideoErrorDomain code:XCDYouTubeErrorNetwork userInfo:userInfo];
[self startNextRequest];
}
#pragma mark - Response Parsing
- (void) handleVideoInfoResponseWithInfo:(NSDictionary *)info response:(NSURLResponse *)response
{
XCDYouTubeLogDebug(@"Handling video info response");
NSError *error = nil;
XCDYouTubeVideo *video = [[XCDYouTubeVideo alloc] initWithIdentifier:self.videoIdentifier info:info playerScript:self.playerScript response:response error:&error];
if (video)
{
self.lastSuccessfulVideo = video;
if (info[@"dashmpd"])
{
NSURL *dashmpdURL = [NSURL URLWithString:(NSString *_Nonnull)info[@"dashmpd"]];
[self startRequestWithURL:dashmpdURL type:XCDYouTubeRequestTypeDashManifest];
return;
}
[video mergeVideo:self.noStreamVideo];
[self finishWithVideo:video];
}
else
{
if ([error.domain isEqual:XCDYouTubeVideoErrorDomain] && error.code == XCDYouTubeErrorUseCipherSignature)
{
self.noStreamVideo = error.userInfo[XCDYouTubeNoStreamVideoUserInfoKey];
[self startWatchPageRequest];
}
else
{
self.lastError = error;
if (error.code > 0)
self.youTubeError = error;
[self startNextRequest];
}
}
}
- (void) handleWebPageWithHTMLString:(NSString *)html
{
XCDYouTubeLogDebug(@"Handling web page response");
self.webpage = [[XCDYouTubeVideoWebpage alloc] initWithHTMLString:html];
if (self.webpage.javaScriptPlayerURL)
{
[self startRequestWithURL:self.webpage.javaScriptPlayerURL type:XCDYouTubeRequestTypeJavaScriptPlayer];
}
else
{
if (self.webpage.isAgeRestricted)
{
NSString *embedURLString = [NSString stringWithFormat:@"https://www.youtube.com/embed/%@", self.videoIdentifier];
[self startRequestWithURL:[NSURL URLWithString:embedURLString] type:XCDYouTubeRequestTypeEmbedPage];
}
else
{
[self startNextRequest];
}
}
}
- (void) handleEmbedWebPageWithHTMLString:(NSString *)html
{
XCDYouTubeLogDebug(@"Handling embed web page response");
self.embedWebpage = [[XCDYouTubeVideoWebpage alloc] initWithHTMLString:html];
if (self.embedWebpage.javaScriptPlayerURL)
{
[self startRequestWithURL:self.embedWebpage.javaScriptPlayerURL type:XCDYouTubeRequestTypeJavaScriptPlayer];
}
else
{
[self startNextRequest];
}
}
- (void) handleJavaScriptPlayerWithScript:(NSString *)script
{
XCDYouTubeLogDebug(@"Handling JavaScript player response");
self.playerScript = [[XCDYouTubePlayerScript alloc] initWithString:script];
if (self.webpage.isAgeRestricted && self.cookies.count == 0)
{
NSString *eurl = [@"https://youtube.googleapis.com/v/" stringByAppendingString:self.videoIdentifier];
NSString *sts = [(NSObject *)self.embedWebpage.playerConfiguration[@"sts"] description] ?: [(NSObject *)self.webpage.playerConfiguration[@"sts"] description] ?: @"";
NSDictionary *query = @{ @"video_id": self.videoIdentifier, @"hl": self.languageIdentifier, @"eurl": eurl, @"sts": sts};
NSString *queryString = XCDQueryStringWithDictionary(query);
NSURL *videoInfoURL = [NSURL URLWithString:[@"https://www.youtube.com/get_video_info?" stringByAppendingString:queryString]];
[self startRequestWithURL:videoInfoURL type:XCDYouTubeRequestTypeGetVideoInfo];
}
else
{
[self handleVideoInfoResponseWithInfo:self.webpage.videoInfo response:nil];
}
}
- (void) handleDashManifestWithXMLString:(NSString *)XMLString response:(NSURLResponse *)response
{
XCDYouTubeLogDebug(@"Handling Dash Manifest response");
XCDYouTubeDashManifestXML *dashManifestXML = [[XCDYouTubeDashManifestXML alloc]initWithXMLString:XMLString];
NSDictionary *dashhManifestStreamURLs = dashManifestXML.streamURLs;
if (dashhManifestStreamURLs)
[self.lastSuccessfulVideo mergeDashManifestStreamURLs:dashhManifestStreamURLs];
[self finishWithVideo:self.lastSuccessfulVideo];
}
#pragma mark - Finish Operation
- (void) finishWithVideo:(XCDYouTubeVideo *)video
{
self.video = video;
XCDYouTubeLogInfo(@"Video operation finished with success: %@", video);
XCDYouTubeLogDebug(@"%@", ^{ return video.debugDescription; }());
[self finish];
}
- (void) finishWithError
{
self.error = self.youTubeError ? YouTubeError(self.youTubeError, self.webpage.regionsAllowed, self.languageIdentifier) : self.lastError;
XCDYouTubeLogError(@"Video operation finished with error: %@\nDomain: %@\nCode: %@\nUser Info: %@", self.error.localizedDescription, self.error.domain, @(self.error.code), self.error.userInfo);
[self finish];
}
- (void) finish
{
self.isExecuting = NO;
self.isFinished = YES;
}
#pragma mark - NSOperation
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key
{
SEL selector = NSSelectorFromString(key);
return selector == @selector(isExecuting) || selector == @selector(isFinished) || [super automaticallyNotifiesObserversForKey:key];
}
- (BOOL) isConcurrent
{
return YES;
}
- (void) start
{
dispatch_semaphore_signal(self.operationStartSemaphore);
if (self.isCancelled)
return;
if (self.videoIdentifier.length != 11)
{
XCDYouTubeLogWarning(@"Video identifier length should be 11. [%@]", self.videoIdentifier);
}
XCDYouTubeLogInfo(@"Starting video operation: %@", self);
self.isExecuting = YES;
self.eventLabels = [[NSMutableArray alloc] initWithArray:@[ @"embedded", @"detailpage" ]];
[self startNextRequest];
}
- (void) cancel
{
if (self.isCancelled || self.isFinished)
return;
XCDYouTubeLogInfo(@"Canceling video operation: %@", self);
[super cancel];
[self.dataTask cancel];
// Wait for `start` to be called in order to avoid this warning: *** XCDYouTubeVideoOperation 0x7f8b18c84880 went isFinished=YES without being started by the queue it is in
dispatch_semaphore_wait(self.operationStartSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(200 * NSEC_PER_MSEC)));
[self finish];
}
#pragma mark - NSObject
- (NSString *) description
{
return [NSString stringWithFormat:@"<%@: %p> %@ (%@)", self.class, self, self.videoIdentifier, self.languageIdentifier];
}
@end

View file

@ -0,0 +1,126 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#if !__has_feature(nullability)
#define NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_END
#define nullable
#define null_resettable
#endif
#import <MediaPlayer/MediaPlayer.h>
NS_ASSUME_NONNULL_BEGIN
/**
* -------------------
* @name Notifications
* -------------------
*/
/**
* NSError key for the `MPMoviePlayerPlaybackDidFinishNotification` userInfo dictionary.
*
* Ideally, there should be a `MPMoviePlayerPlaybackDidFinishErrorUserInfoKey` declared near to `MPMoviePlayerPlaybackDidFinishReasonUserInfoKey` in MPMoviePlayerController.h but since it doesn't exist, here is a convenient constant key.
*/
MP_EXTERN NSString *const XCDMoviePlayerPlaybackDidFinishErrorUserInfoKey;
/**
* Posted when the video player has received the video information. The `object` of the notification is the `XCDYouTubeVideoPlayerViewController` instance. The `userInfo` dictionary contains the `XCDYouTubeVideo` object.
*/
MP_EXTERN NSString *const XCDYouTubeVideoPlayerViewControllerDidReceiveVideoNotification;
/**
* The key for the `XCDYouTubeVideo` object in the user info dictionary of `XCDYouTubeVideoPlayerViewControllerDidReceiveVideoNotification`.
*/
MP_EXTERN NSString *const XCDYouTubeVideoUserInfoKey;
/**
* A subclass of `MPMoviePlayerViewController` for playing YouTube videos.
*
* Use UIViewControllers `presentMoviePlayerViewControllerAnimated:` method to play a YouTube video fullscreen.
*
* Use the `<presentInView:>` method to play a YouTube video inline.
*/
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
DEPRECATED_MSG_ATTRIBUTE("Use `AVFoundation` or `AVKit` APIs instead.")
@interface XCDYouTubeVideoPlayerViewController : MPMoviePlayerViewController
#pragma clang diagnostic pop
/**
* ------------------
* @name Initializing
* ------------------
*/
/**
* Initializes a YouTube video player view controller
*
* @param videoIdentifier A 11 characters YouTube video identifier. If the video identifier is invalid the `MPMoviePlayerPlaybackDidFinishNotification` will be posted with a `MPMovieFinishReasonPlaybackError` reason.
*
* @return An initialized YouTube video player view controller with the specified video identifier.
*
* @discussion You can pass a nil *videoIdentifier* (or use the standard `init` method instead) and set the `<videoIdentifier>` property later.
*/
- (instancetype) initWithVideoIdentifier:(nullable NSString *)videoIdentifier NS_DESIGNATED_INITIALIZER;
/**
* ------------------------------------
* @name Accessing the video identifier
* ------------------------------------
*/
/**
* The 11 characters YouTube video identifier.
*/
@property (nonatomic, copy, nullable) NSString *videoIdentifier;
/**
* ------------------------------------------
* @name Defining the preferred video quality
* ------------------------------------------
*/
/**
* The preferred order for the quality of the video to play. Plays the first match when multiple video streams are available.
*
* Defaults to @[ XCDYouTubeVideoQualityHTTPLiveStreaming, @(XCDYouTubeVideoQualityHD720), @(XCDYouTubeVideoQualityMedium360), @(XCDYouTubeVideoQualitySmall240) ]
*
* You should set this property right after calling the `<initWithVideoIdentifier:>` method. Setting this property to nil restores its default values.
*
* @see XCDYouTubeVideoQuality
*/
@property (nonatomic, copy, null_resettable) NSArray *preferredVideoQualities;
/**
* ------------------------
* @name Presenting a video
* ------------------------
*/
/**
* Present the video inside a view.
*
* @param view The view inside which you want to present the video.
*
* @discussion The video view is added as a subview of the specified view. The video does not start playing immediately, you have to call `[videoPlayerViewController.moviePlayer play]` for playback to start. See `MPMoviePlayerController` documentation for more information.
*
* Ownership of the XCDYouTubeVideoPlayerViewController instance is transferred to the view.
*/
- (void) presentInView:(UIView *)view;
@end
/**
* ------------------------------
* @name Deprecated notifications
* ------------------------------
*/
MP_EXTERN NSString *const XCDYouTubeVideoPlayerViewControllerDidReceiveMetadataNotification DEPRECATED_MSG_ATTRIBUTE("Use XCDYouTubeVideoPlayerViewControllerDidReceiveVideoNotification instead.");
MP_EXTERN NSString *const XCDMetadataKeyTitle DEPRECATED_MSG_ATTRIBUTE("Use XCDYouTubeVideoUserInfoKey instead.");
MP_EXTERN NSString *const XCDMetadataKeySmallThumbnailURL DEPRECATED_MSG_ATTRIBUTE("Use XCDYouTubeVideoUserInfoKey instead.");
MP_EXTERN NSString *const XCDMetadataKeyMediumThumbnailURL DEPRECATED_MSG_ATTRIBUTE("Use XCDYouTubeVideoUserInfoKey instead.");
MP_EXTERN NSString *const XCDMetadataKeyLargeThumbnailURL DEPRECATED_MSG_ATTRIBUTE("Use XCDYouTubeVideoUserInfoKey instead.");
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,209 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import "XCDYouTubeVideoPlayerViewController.h"
#import "XCDYouTubeClient.h"
#import <objc/runtime.h>
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSString *const XCDMoviePlayerPlaybackDidFinishErrorUserInfoKey = @"error"; // documented in -[MPMoviePlayerController initWithContentURL:]
NSString *const XCDYouTubeVideoPlayerViewControllerDidReceiveMetadataNotification = @"XCDYouTubeVideoPlayerViewControllerDidReceiveMetadataNotification";
NSString *const XCDMetadataKeyTitle = @"Title";
NSString *const XCDMetadataKeySmallThumbnailURL = @"SmallThumbnailURL";
NSString *const XCDMetadataKeyMediumThumbnailURL = @"MediumThumbnailURL";
NSString *const XCDMetadataKeyLargeThumbnailURL = @"LargeThumbnailURL";
NSString *const XCDYouTubeVideoPlayerViewControllerDidReceiveVideoNotification = @"XCDYouTubeVideoPlayerViewControllerDidReceiveVideoNotification";
NSString *const XCDYouTubeVideoUserInfoKey = @"Video";
@interface XCDYouTubeVideoPlayerViewController ()
@property (nonatomic, weak) id<XCDYouTubeOperation> videoOperation;
@property (nonatomic, assign, getter = isEmbedded) BOOL embedded;
@end
@implementation XCDYouTubeVideoPlayerViewController
/*
* MPMoviePlayerViewController on iOS 7 and earlier
* - (id) init
* `-- [super init]
*
* - (id) initWithContentURL:(NSURL *)contentURL
* |-- [self init]
* `-- [self.moviePlayer setContentURL:contentURL]
*
* MPMoviePlayerViewController on iOS 8 and later
* - (id) init
* `-- [self initWithContentURL:nil]
*
* - (id) initWithContentURL:(NSURL *)contentURL
* |-- [super init]
* `-- [self.moviePlayer setContentURL:contentURL]
*/
- (instancetype) init
{
return [self initWithVideoIdentifier:nil];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
- (instancetype) initWithContentURL:(NSURL *)contentURL
{
@throw [NSException exceptionWithName:NSGenericException reason:@"Use the `initWithVideoIdentifier:` method instead." userInfo:nil];
} // LCOV_EXCL_LINE
- (instancetype) initWithVideoIdentifier:(NSString *)videoIdentifier
{
#if defined(DEBUG) && DEBUG
NSString *callStackSymbols = [[NSThread callStackSymbols] componentsJoinedByString:@"\n"];
if (([callStackSymbols rangeOfString:@"-[XCDYouTubeClient getVideoWithIdentifier:completionHandler:]_block_invoke"].length > 0) || ([callStackSymbols rangeOfString:@"-[XCDYouTubeClient getVideoWithIdentifier:cookies:completionHandler:]_block_invoke"].length > 0))
{
NSString *reason = @"XCDYouTubeVideoPlayerViewController must not be used in the completion handler of `-[XCDYouTubeClient getVideoWithIdentifier:completionHandler:]` or `-[XCDYouTubeClient getVideoWithIdentifier:cookies:completionHandler:]`. Please read the documentation and sample code to properly use XCDYouTubeVideoPlayerViewController.";
@throw [NSException exceptionWithName:NSGenericException reason:reason userInfo:nil];
}
#endif
if ([[[UIDevice currentDevice] systemVersion] integerValue] >= 8)
self = [super initWithContentURL:nil];
else
self = [super init]; // LCOV_EXCL_LINE
if (!self)
return nil; // LCOV_EXCL_LINE
// See https://github.com/0xced/XCDYouTubeKit/commit/cadec1c3857d6a302f71b9ce7d1ae48e389e6890
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
if (videoIdentifier)
self.videoIdentifier = videoIdentifier;
return self;
}
#pragma clang diagnostic pop
#pragma mark - Public
- (NSArray *) preferredVideoQualities
{
if (!_preferredVideoQualities)
_preferredVideoQualities = @[ XCDYouTubeVideoQualityHTTPLiveStreaming, @(XCDYouTubeVideoQualityHD720), @(XCDYouTubeVideoQualityMedium360), @(XCDYouTubeVideoQualitySmall240) ];
return _preferredVideoQualities;
}
- (void) setVideoIdentifier:(NSString *)videoIdentifier
{
if ([videoIdentifier isEqual:self.videoIdentifier])
return;
_videoIdentifier = [videoIdentifier copy];
[self.videoOperation cancel];
self.videoOperation = [[XCDYouTubeClient defaultClient] getVideoWithIdentifier:videoIdentifier completionHandler:^(XCDYouTubeVideo *video, NSError *error)
{
if (video)
{
NSURL *streamURL = nil;
for (NSNumber *videoQuality in self.preferredVideoQualities)
{
streamURL = video.streamURLs[videoQuality];
if (streamURL)
{
[self startVideo:video streamURL:streamURL];
break;
}
}
if (!streamURL)
{
NSError *noStreamError = [NSError errorWithDomain:XCDYouTubeVideoErrorDomain code:XCDYouTubeErrorNoStreamAvailable userInfo:nil];
[self stopWithError:noStreamError];
}
}
else
{
[self stopWithError:error];
}
}];
}
- (void) presentInView:(UIView *)view
{
static const void * const XCDYouTubeVideoPlayerViewControllerKey = &XCDYouTubeVideoPlayerViewControllerKey;
self.embedded = YES;
self.moviePlayer.controlStyle = MPMovieControlStyleEmbedded;
self.moviePlayer.view.frame = CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height);
self.moviePlayer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
if (![view.subviews containsObject:self.moviePlayer.view])
[view addSubview:self.moviePlayer.view];
objc_setAssociatedObject(view, XCDYouTubeVideoPlayerViewControllerKey, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark - Private
- (void) startVideo:(XCDYouTubeVideo *)video streamURL:(NSURL *)streamURL
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSMutableDictionary *userInfo = [NSMutableDictionary new];
if (video.title)
userInfo[XCDMetadataKeyTitle] = video.title;
if (video.smallThumbnailURL)
userInfo[XCDMetadataKeySmallThumbnailURL] = video.smallThumbnailURL;
if (video.mediumThumbnailURL)
userInfo[XCDMetadataKeyMediumThumbnailURL] = video.mediumThumbnailURL;
if (video.largeThumbnailURL)
userInfo[XCDMetadataKeyLargeThumbnailURL] = video.largeThumbnailURL;
[[NSNotificationCenter defaultCenter] postNotificationName:XCDYouTubeVideoPlayerViewControllerDidReceiveMetadataNotification object:self userInfo:userInfo];
#pragma clang diagnostic pop
self.moviePlayer.contentURL = streamURL;
[[NSNotificationCenter defaultCenter] postNotificationName:XCDYouTubeVideoPlayerViewControllerDidReceiveVideoNotification object:self userInfo:@{ XCDYouTubeVideoUserInfoKey: video }];
}
- (void) stopWithError:(NSError *)error
{
NSDictionary *userInfo = @{ MPMoviePlayerPlaybackDidFinishReasonUserInfoKey: @(MPMovieFinishReasonPlaybackError),
XCDMoviePlayerPlaybackDidFinishErrorUserInfoKey: error };
[[NSNotificationCenter defaultCenter] postNotificationName:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer userInfo:userInfo];
if (self.isEmbedded)
[self.moviePlayer.view removeFromSuperview];
else
[self.presentingViewController dismissMoviePlayerViewControllerAnimated];
}
#pragma mark - UIViewController
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (![self isBeingPresented])
return;
self.moviePlayer.controlStyle = MPMovieControlStyleFullscreen;
[self.moviePlayer play];
}
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (![self isBeingDismissed])
return;
[self.videoOperation cancel];
}
@end

View file

@ -0,0 +1,18 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import <Foundation/Foundation.h>
__attribute__((visibility("hidden")))
@interface XCDYouTubeVideoWebpage : NSObject
- (instancetype) initWithHTMLString:(NSString *)html;
@property (nonatomic, readonly) NSDictionary *playerConfiguration;
@property (nonatomic, readonly) NSDictionary *videoInfo;
@property (nonatomic, readonly) NSURL *javaScriptPlayerURL;
@property (nonatomic, readonly) BOOL isAgeRestricted;
@property (nonatomic, readonly) NSSet *regionsAllowed;
@end

View file

@ -0,0 +1,124 @@
//
// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved.
//
#import "XCDYouTubeVideoWebpage.h"
@interface XCDYouTubeVideoWebpage ()
@property (nonatomic, readonly) NSString *html;
@end
@implementation XCDYouTubeVideoWebpage
@synthesize playerConfiguration = _playerConfiguration;
@synthesize videoInfo = _videoInfo;
@synthesize javaScriptPlayerURL = _javaScriptPlayerURL;
@synthesize isAgeRestricted = _isAgeRestricted;
@synthesize regionsAllowed = _regionsAllowed;
- (instancetype) initWithHTMLString:(NSString *)html
{
if (!(self = [super init]))
return nil; // LCOV_EXCL_LINE
_html = html;
return self;
}
- (NSDictionary *) playerConfiguration
{
if (!_playerConfiguration)
{
__block NSDictionary *playerConfigurationDictionary;
NSRegularExpression *playerConfigRegularExpression = [NSRegularExpression regularExpressionWithPattern:@"ytplayer.config\\s*=\\s*(\\{.*?\\});|[\\({]\\s*'PLAYER_CONFIG'[,:]\\s*(\\{.*?\\})\\s*(?:,'|\\))" options:NSRegularExpressionCaseInsensitive error:NULL];
[playerConfigRegularExpression enumerateMatchesInString:self.html options:(NSMatchingOptions)0 range:NSMakeRange(0, self.html.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop)
{
for (NSUInteger i = 1; i < result.numberOfRanges; i++)
{
NSRange range = [result rangeAtIndex:i];
if (range.length == 0)
continue;
NSString *configString = [self.html substringWithRange:range];
NSData *configData = [configString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *playerConfiguration = [NSJSONSerialization JSONObjectWithData:configData ?: [NSData new] options:(NSJSONReadingOptions)0 error:NULL];
if ([playerConfiguration isKindOfClass:[NSDictionary class]])
{
playerConfigurationDictionary = playerConfiguration;
*stop = YES;
}
}
}];
_playerConfiguration = playerConfigurationDictionary;
}
return _playerConfiguration;
}
- (NSDictionary *) videoInfo
{
if (!_videoInfo)
{
NSDictionary *args = self.playerConfiguration[@"args"];
if ([args isKindOfClass:[NSDictionary class]])
{
NSMutableDictionary *info = [NSMutableDictionary new];
[args enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop)
{
if ([(NSObject *)value isKindOfClass:[NSString class]] || [(NSObject *)value isKindOfClass:[NSNumber class]])
info[key] = [(NSObject *)value description];
}];
_videoInfo = [info copy];
}
}
return _videoInfo;
}
- (NSURL *) javaScriptPlayerURL
{
if (!_javaScriptPlayerURL)
{
NSString *jsAssets = [self.playerConfiguration valueForKeyPath:@"assets.js"];
if ([jsAssets isKindOfClass:[NSString class]])
{
NSString *javaScriptPlayerURLString = jsAssets;
if ([jsAssets hasPrefix:@"//"])
javaScriptPlayerURLString = [@"https:" stringByAppendingString:jsAssets];
else if ([jsAssets hasPrefix:@"/"])
javaScriptPlayerURLString = [@"https://www.youtube.com" stringByAppendingString:jsAssets];
_javaScriptPlayerURL = [NSURL URLWithString:javaScriptPlayerURLString];
}
}
return _javaScriptPlayerURL;
}
- (BOOL) isAgeRestricted
{
if (!_isAgeRestricted)
{
NSStringCompareOptions options = (NSStringCompareOptions)0;
NSRange range = NSMakeRange(0, self.html.length);
_isAgeRestricted = [self.html rangeOfString:@"og:restrictions:age" options:options range:range].location != NSNotFound;
}
return _isAgeRestricted;
}
- (NSSet *) regionsAllowed
{
if (!_regionsAllowed)
{
_regionsAllowed = [NSSet set];
NSRegularExpression *regionsAllowedRegularExpression = [NSRegularExpression regularExpressionWithPattern:@"meta\\s+itemprop=\"regionsAllowed\"\\s+content=\"(.*)\"" options:(NSRegularExpressionOptions)0 error:NULL];
NSTextCheckingResult *regionsAllowedResult = [regionsAllowedRegularExpression firstMatchInString:self.html options:(NSMatchingOptions)0 range:NSMakeRange(0, self.html.length)];
if (regionsAllowedResult.numberOfRanges > 1)
{
NSString *regionsAllowed = [self.html substringWithRange:[regionsAllowedResult rangeAtIndex:1]];
if (regionsAllowed.length > 0)
_regionsAllowed = [NSSet setWithArray:[regionsAllowed componentsSeparatedByString:@","]];
}
}
return _regionsAllowed;
}
@end

View file

@ -0,0 +1,13 @@
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,59 @@
# YoutubePlayer-in-WKWebView
[![YoutubePlayer-in-WKWebView version](https://img.shields.io/cocoapods/v/YoutubePlayer-in-WKWebView.svg?style=plastic)](http://cocoadocs.org/docsets/YoutubePlayer-in-WKWebView) [![YoutubePlayer-in-WKWebView platform](https://img.shields.io/cocoapods/p/YoutubePlayer-in-WKWebView.svg?style=plastic)](http://cocoadocs.org/docsets/YoutubePlayer-in-WKWebView) [![ios 8.0](https://img.shields.io/badge/ios-8.0-blue.svg?style=plastic)](http://cocoadocs.org/docsets/YoutubePlayer-in-WKWebView)
YoutubePlayer-in-WKWebView is forked from [youtube-ios-player-helper](https://github.com/youtube/youtube-ios-player-helper) For using WKWebView.
## Changes from [youtube-ios-player-helper](https://github.com/youtube/youtube-ios-player-helper)
- class prefix changed to WKYT from YT. `YTPlayerView` -> `WKYTPlayerView`
- using WKWebView instead of UIWebView.
- getting properties asynchronously.
```
// YTPlayerView
NSTimeInterval duration = self.playerView.duration;
// WKYTPlayerView
[self.playerView getDuration:^(NSTimeInterval duration, NSError * _Nullable error) {
if (!error) {
float seekToTime = duration * self.slider.value;
}
}];
```
## Usage
To run the example project; clone the repo, and run `pod install` from the Project directory first. For a simple tutorial see this Google Developers article - [Using the YouTube Helper Library to embed YouTube videos in your iOS application](https://developers.google.com/youtube/v3/guides/ios_youtube_helper).
## Requirements
## Installation
YouTube-Player-iOS-Helper is available through [CocoaPods](http://cocoapods.org), to install
it simply add the following line to your Podfile:
pod "YoutubePlayer-in-WKWebView", "~> 0.3.0"
After installing in your project and opening the workspace, to use the library:
1. Drag a UIView the desired size of your player onto your Storyboard.
2. Change the UIView's class in the Identity Inspector tab to WKYTPlayerView
3. Import "WKYTPlayerView.h" in your ViewController.
4. Add the following property to your ViewController's header file:
```objc
@property(nonatomic, strong) IBOutlet WKYTPlayerView *playerView;
```
5. Load the video into the player in your controller's code with the following code:
```objc
[self.playerView loadWithVideoId:@"M7lc1UVf-VE"];
```
6. Run your code!
See the sample project for more advanced uses, including passing additional player parameters, custom html and
working with callbacks via WKYTPlayerViewDelegate.
## License
YoutubePlayer-in-WKWebView is available under the Apache 2.0 license. See the LICENSE file for more info.

View file

@ -0,0 +1,90 @@
<!--
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE html>
<html>
<head>
<style>
body { margin: 0; width:100%%; height:100%%; background-color:#000000; }
html { width:100%%; height:100%%; background-color:#000000; }
.embed-container iframe,
.embed-container object,
.embed-container embed {
position: absolute;
top: 0;
left: 0;
width: 100%% !important;
height: 100%% !important;
}
</style>
</head>
<body>
<div class="embed-container">
<div id="player"></div>
</div>
<script src="https://www.youtube.com/iframe_api" onerror="window.location.href='ytplayer://onYouTubeIframeAPIFailedToLoad'"></script>
<script>
var player;
var error = false;
YT.ready(function() {
player = new YT.Player('player', %@);
player.setSize(window.innerWidth, window.innerHeight);
window.location.href = 'ytplayer://onYouTubeIframeAPIReady';
// this will transmit playTime frequently while playng
function getCurrentTime() {
var state = player.getPlayerState();
if (state == YT.PlayerState.PLAYING) {
time = player.getCurrentTime()
window.location.href = 'ytplayer://onPlayTime?data=' + time;
}
}
window.setInterval(getCurrentTime, 500);
});
function onReady(event) {
window.location.href = 'ytplayer://onReady?data=' + event.data;
}
function onStateChange(event) {
if (!error) {
window.location.href = 'ytplayer://onStateChange?data=' + event.data;
}
else {
error = false;
}
}
function onPlaybackQualityChange(event) {
window.location.href = 'ytplayer://onPlaybackQualityChange?data=' + event.data;
}
function onPlayerError(event) {
if (event.data == 100) {
error = true;
}
window.location.href = 'ytplayer://onError?data=' + event.data;
}
window.onresize = function() {
player.setSize(window.innerWidth, window.innerHeight);
}
</script>
</body>
</html>

View file

@ -0,0 +1,734 @@
// Copyright © 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
@class WKYTPlayerView;
/** These enums represent the state of the current video in the player. */
typedef NS_ENUM(NSInteger, WKYTPlayerState) {
kWKYTPlayerStateUnstarted,
kWKYTPlayerStateEnded,
kWKYTPlayerStatePlaying,
kWKYTPlayerStatePaused,
kWKYTPlayerStateBuffering,
kWKYTPlayerStateQueued,
kWKYTPlayerStateUnknown
};
/** These enums represent the resolution of the currently loaded video. */
typedef NS_ENUM(NSInteger, WKYTPlaybackQuality) {
kWKYTPlaybackQualitySmall,
kWKYTPlaybackQualityMedium,
kWKYTPlaybackQualityLarge,
kWKYTPlaybackQualityHD720,
kWKYTPlaybackQualityHD1080,
kWKYTPlaybackQualityHighRes,
kWKYTPlaybackQualityAuto, /** Addition for YouTube Live Events. */
kWKYTPlaybackQualityDefault,
kWKYTPlaybackQualityUnknown /** This should never be returned. It is here for future proofing. */
};
/** These enums represent error codes thrown by the player. */
typedef NS_ENUM(NSInteger, WKYTPlayerError) {
kWKYTPlayerErrorInvalidParam,
kWKYTPlayerErrorHTML5Error,
kWKYTPlayerErrorVideoNotFound, // Functionally equivalent error codes 100 and
// 105 have been collapsed into |kWKYTPlayerErrorVideoNotFound|.
kWKYTPlayerErrorNotEmbeddable, // Functionally equivalent error codes 101 and
// 150 have been collapsed into |kWKYTPlayerErrorNotEmbeddable|.
kWKYTPlayerErrorUnknown
};
/**
* A delegate for ViewControllers to respond to YouTube player events outside
* of the view, such as changes to video playback state or playback errors.
* The callback functions correlate to the events fired by the IFrame API.
* For the full documentation, see the IFrame documentation here:
* https://developers.google.com/youtube/iframe_api_reference#Events
*/
@protocol WKYTPlayerViewDelegate<NSObject>
@optional
/**
* Invoked when the player view is ready to receive API calls.
*
* @param playerView The WKYTPlayerView instance that has become ready.
*/
- (void)playerViewDidBecomeReady:(nonnull WKYTPlayerView *)playerView;
/**
* Callback invoked when player state has changed, e.g. stopped or started playback.
*
* @param playerView The WKYTPlayerView instance where playback state has changed.
* @param state WKYTPlayerState designating the new playback state.
*/
- (void)playerView:(nonnull WKYTPlayerView *)playerView didChangeToState:(WKYTPlayerState)state;
/**
* Callback invoked when playback quality has changed.
*
* @param playerView The WKYTPlayerView instance where playback quality has changed.
* @param quality WKYTPlaybackQuality designating the new playback quality.
*/
- (void)playerView:(nonnull WKYTPlayerView *)playerView didChangeToQuality:(WKYTPlaybackQuality)quality;
/**
* Callback invoked when an error has occured.
*
* @param playerView The WKYTPlayerView instance where the error has occurred.
* @param error WKYTPlayerError containing the error state.
*/
- (void)playerView:(nonnull WKYTPlayerView *)playerView receivedError:(WKYTPlayerError)error;
/**
* Callback invoked frequently when playBack is plaing.
*
* @param playerView The WKYTPlayerView instance where the error has occurred.
* @param playTime float containing curretn playback time.
*/
- (void)playerView:(nonnull WKYTPlayerView *)playerView didPlayTime:(float)playTime;
/**
* Callback invoked when setting up the webview to allow custom colours so it fits in
* with app color schemes. If a transparent view is required specify clearColor and
* the code will handle the opacity etc.
*
* @param playerView The WKYTPlayerView instance where the error has occurred.
* @return A color object that represents the background color of the webview.
*/
- (nonnull UIColor *)playerViewPreferredWebViewBackgroundColor:(nonnull WKYTPlayerView *)playerView;
/**
* Callback invoked when initially loading the YouTube iframe to the webview to display a custom
* loading view while the player view is not ready. This loading view will be dismissed just before
* -playerViewDidBecomeReady: callback is invoked. The loading view will be automatically resized
* to cover the entire player view.
*
* The default implementation does not display any custom loading views so the player will display
* a blank view with a background color of (-playerViewPreferredWebViewBackgroundColor:).
*
* Note that the custom loading view WILL NOT be displayed after iframe is loaded. It will be
* handled by YouTube iframe API. This callback is just intended to tell users the view is actually
* doing something while iframe is being loaded, which will take some time if users are in poor networks.
*
* @param playerView The WKYTPlayerView instance where the error has occurred.
* @return A view object that will be displayed while YouTube iframe API is being loaded.
* Pass nil to display no custom loading view. Default implementation returns nil.
*/
- (nullable UIView *)playerViewPreferredInitialLoadingView:(nonnull WKYTPlayerView *)playerView;
/**
* Callback invoked when an api loading error has occured.
*
* @param playerView The WKYTPlayerView instance where the error has occurred.
*/
- (void)playerViewIframeAPIDidFailedToLoad:(nonnull WKYTPlayerView *)playerView;
@end
/**
* WKYTPlayerView is a custom UIView that client developers will use to include YouTube
* videos in their iOS applications. It can be instantiated programmatically, or via
* Interface Builder. Use the methods WKYTPlayerView::loadWithVideoId:,
* WKYTPlayerView::loadWithPlaylistId: or their variants to set the video or playlist
* to populate the view with.
*/
@interface WKYTPlayerView : UIView<WKNavigationDelegate, WKUIDelegate>
@property(nonatomic, strong, nullable, readonly) WKWebView *webView;
/** A delegate to be notified on playback events. */
@property(nonatomic, weak, nullable) id<WKYTPlayerViewDelegate> delegate;
/**
* This method loads the player with the given video ID.
* This is a convenience method for calling WKYTPlayerView::loadPlayerWithVideoId:withPlayerVars:
* without player variables.
*
* This method reloads the entire contents of the WKWebView and regenerates its HTML contents.
* To change the currently loaded video without reloading the entire WKWebView, use the
* WKYTPlayerView::cueVideoById:startSeconds:suggestedQuality: family of methods.
*
* @param videoId The YouTube video ID of the video to load in the player view.
* @return YES if player has been configured correctly, NO otherwise.
*/
- (BOOL)loadWithVideoId:(nonnull NSString *)videoId;
/**
* This method loads the player with the given playlist ID.
* This is a convenience method for calling WKYTPlayerView::loadWithPlaylistId:withPlayerVars:
* without player variables.
*
* This method reloads the entire contents of the WKWebView and regenerates its HTML contents.
* To change the currently loaded video without reloading the entire WKWebView, use the
* WKYTPlayerView::cuePlaylistByPlaylistId:index:startSeconds:suggestedQuality:
* family of methods.
*
* @param playlistId The YouTube playlist ID of the playlist to load in the player view.
* @return YES if player has been configured correctly, NO otherwise.
*/
- (BOOL)loadWithPlaylistId:(nonnull NSString *)playlistId;
/**
* This method loads the player with the given video ID and player variables. Player variables
* specify optional parameters for video playback. For instance, to play a YouTube
* video inline, the following playerVars dictionary would be used:
*
* @code
* @{ @"playsinline" : @1 };
* @endcode
*
* Note that when the documentation specifies a valid value as a number (typically 0, 1 or 2),
* both strings and integers are valid values. The full list of parameters is defined at:
* https://developers.google.com/youtube/player_parameters?playerVersion=HTML5.
*
* This method reloads the entire contents of the WKWebView and regenerates its HTML contents.
* To change the currently loaded video without reloading the entire WKWebView, use the
* WKYTPlayerView::cueVideoById:startSeconds:suggestedQuality: family of methods.
*
* @param videoId The YouTube video ID of the video to load in the player view.
* @param playerVars An NSDictionary of player parameters.
* @return YES if player has been configured correctly, NO otherwise.
*/
- (BOOL)loadWithVideoId:(nonnull NSString *)videoId playerVars:(nullable NSDictionary *)playerVars;
/**
* This method loads the player with the given video ID and player variables. Player variables
* specify optional parameters for video playback. For instance, to play a YouTube
* video inline, the following playerVars dictionary would be used:
*
* @code
* @{ @"playsinline" : @1 };
* @endcode
*
* Note that when the documentation specifies a valid value as a number (typically 0, 1 or 2),
* both strings and integers are valid values. The full list of parameters is defined at:
* https://developers.google.com/youtube/player_parameters?playerVersion=HTML5.
*
* This method reloads the entire contents of the WKWebView and regenerates its HTML contents.
* To change the currently loaded video without reloading the entire WKWebView, use the
* WKYTPlayerView::cueVideoById:startSeconds:suggestedQuality: family of methods.
*
* @param videoId The YouTube video ID of the video to load in the player view.
* @param playerVars An NSDictionary of player parameters.
* @param path String with the path for HTML template used for viewing video.
* @return YES if player has been configured correctly, NO otherwise.
*/
- (BOOL)loadWithVideoId:(nonnull NSString *)videoId playerVars:(nullable NSDictionary *)playerVars templatePath:(nullable NSString *)path;
/**
* This method loads the player with the given playlist ID and player variables. Player variables
* specify optional parameters for video playback. For instance, to play a YouTube
* video inline, the following playerVars dictionary would be used:
*
* @code
* @{ @"playsinline" : @1 };
* @endcode
*
* Note that when the documentation specifies a valid value as a number (typically 0, 1 or 2),
* both strings and integers are valid values. The full list of parameters is defined at:
* https://developers.google.com/youtube/player_parameters?playerVersion=HTML5.
*
* This method reloads the entire contents of the WKWebView and regenerates its HTML contents.
* To change the currently loaded video without reloading the entire WKWebView, use the
* WKYTPlayerView::cuePlaylistByPlaylistId:index:startSeconds:suggestedQuality:
* family of methods.
*
* @param playlistId The YouTube playlist ID of the playlist to load in the player view.
* @param playerVars An NSDictionary of player parameters.
* @return YES if player has been configured correctly, NO otherwise.
*/
- (BOOL)loadWithPlaylistId:(nonnull NSString *)playlistId playerVars:(nullable NSDictionary *)playerVars;
/**
* This method loads an iframe player with the given player parameters. Usually you may want to use
* -loadWithVideoId:playerVars: or -loadWithPlaylistId:playerVars: instead of this method does not handle
* video_id or playlist_id at all. The full list of parameters is defined at:
* https://developers.google.com/youtube/player_parameters?playerVersion=HTML5.
*
* @param additionalPlayerParams An NSDictionary of parameters in addition to required parameters
* to instantiate the HTML5 player with. This differs depending on
* whether a single video or playlist is being loaded.
* @return YES if successful, NO if not.
*/
- (BOOL)loadWithPlayerParams:(nullable NSDictionary *)additionalPlayerParams;
#pragma mark - Player controls
// These methods correspond to their JavaScript equivalents as documented here:
// https://developers.google.com/youtube/iframe_api_reference#Playback_controls
/**
* Starts or resumes playback on the loaded video. Corresponds to this method from
* the JavaScript API:
* https://developers.google.com/youtube/iframe_api_reference#playVideo
*/
- (void)playVideo;
/**
* Pauses playback on a playing video. Corresponds to this method from
* the JavaScript API:
* https://developers.google.com/youtube/iframe_api_reference#pauseVideo
*/
- (void)pauseVideo;
/**
* Stops playback on a playing video. Corresponds to this method from
* the JavaScript API:
* https://developers.google.com/youtube/iframe_api_reference#stopVideo
*/
- (void)stopVideo;
/**
* Seek to a given time on a playing video. Corresponds to this method from
* the JavaScript API:
* https://developers.google.com/youtube/iframe_api_reference#seekTo
*
* @param seekToSeconds The time in seconds to seek to in the loaded video.
* @param allowSeekAhead Whether to make a new request to the server if the time is
* outside what is currently buffered. Recommended to set to YES.
*/
- (void)seekToSeconds:(float)seekToSeconds allowSeekAhead:(BOOL)allowSeekAhead;
#pragma mark - Queuing videos
// Queueing functions for videos. These methods correspond to their JavaScript
// equivalents as documented here:
// https://developers.google.com/youtube/iframe_api_reference#Queueing_Functions
/**
* Cues a given video by its video ID for playback starting at the given time and with the
* suggested quality. Cueing loads a video, but does not start video playback. This method
* corresponds with its JavaScript API equivalent as documented here:
* https://developers.google.com/youtube/iframe_api_reference#cueVideoById
*
* @param videoId A video ID to cue.
* @param startSeconds Time in seconds to start the video when WKYTPlayerView::playVideo is called.
* @param suggestedQuality WKYTPlaybackQuality value suggesting a playback quality.
*/
- (void)cueVideoById:(nonnull NSString *)videoId
startSeconds:(float)startSeconds
suggestedQuality:(WKYTPlaybackQuality)suggestedQuality;
/**
* Cues a given video by its video ID for playback starting and ending at the given times
* with the suggested quality. Cueing loads a video, but does not start video playback. This
* method corresponds with its JavaScript API equivalent as documented here:
* https://developers.google.com/youtube/iframe_api_reference#cueVideoById
*
* @param videoId A video ID to cue.
* @param startSeconds Time in seconds to start the video when playVideo() is called.
* @param endSeconds Time in seconds to end the video after it begins playing.
* @param suggestedQuality WKYTPlaybackQuality value suggesting a playback quality.
*/
- (void)cueVideoById:(nonnull NSString *)videoId
startSeconds:(float)startSeconds
endSeconds:(float)endSeconds
suggestedQuality:(WKYTPlaybackQuality)suggestedQuality;
/**
* Loads a given video by its video ID for playback starting at the given time and with the
* suggested quality. Loading a video both loads it and begins playback. This method
* corresponds with its JavaScript API equivalent as documented here:
* https://developers.google.com/youtube/iframe_api_reference#loadVideoById
*
* @param videoId A video ID to load and begin playing.
* @param startSeconds Time in seconds to start the video when it has loaded.
* @param suggestedQuality WKYTPlaybackQuality value suggesting a playback quality.
*/
- (void)loadVideoById:(nonnull NSString *)videoId
startSeconds:(float)startSeconds
suggestedQuality:(WKYTPlaybackQuality)suggestedQuality;
/**
* Loads a given video by its video ID for playback starting and ending at the given times
* with the suggested quality. Loading a video both loads it and begins playback. This method
* corresponds with its JavaScript API equivalent as documented here:
* https://developers.google.com/youtube/iframe_api_reference#loadVideoById
*
* @param videoId A video ID to load and begin playing.
* @param startSeconds Time in seconds to start the video when it has loaded.
* @param endSeconds Time in seconds to end the video after it begins playing.
* @param suggestedQuality WKYTPlaybackQuality value suggesting a playback quality.
*/
- (void)loadVideoById:(nonnull NSString *)videoId
startSeconds:(float)startSeconds
endSeconds:(float)endSeconds
suggestedQuality:(WKYTPlaybackQuality)suggestedQuality;
/**
* Cues a given video by its URL on YouTube.com for playback starting at the given time
* and with the suggested quality. Cueing loads a video, but does not start video playback.
* This method corresponds with its JavaScript API equivalent as documented here:
* https://developers.google.com/youtube/iframe_api_reference#cueVideoByUrl
*
* @param videoURL URL of a YouTube video to cue for playback.
* @param startSeconds Time in seconds to start the video when WKYTPlayerView::playVideo is called.
* @param suggestedQuality WKYTPlaybackQuality value suggesting a playback quality.
*/
- (void)cueVideoByURL:(nonnull NSString *)videoURL
startSeconds:(float)startSeconds
suggestedQuality:(WKYTPlaybackQuality)suggestedQuality;
/**
* Cues a given video by its URL on YouTube.com for playback starting at the given time
* and with the suggested quality. Cueing loads a video, but does not start video playback.
* This method corresponds with its JavaScript API equivalent as documented here:
* https://developers.google.com/youtube/iframe_api_reference#cueVideoByUrl
*
* @param videoURL URL of a YouTube video to cue for playback.
* @param startSeconds Time in seconds to start the video when WKYTPlayerView::playVideo is called.
* @param endSeconds Time in seconds to end the video after it begins playing.
* @param suggestedQuality WKYTPlaybackQuality value suggesting a playback quality.
*/
- (void)cueVideoByURL:(nonnull NSString *)videoURL
startSeconds:(float)startSeconds
endSeconds:(float)endSeconds
suggestedQuality:(WKYTPlaybackQuality)suggestedQuality;
/**
* Loads a given video by its video ID for playback starting at the given time
* with the suggested quality. Loading a video both loads it and begins playback. This method
* corresponds with its JavaScript API equivalent as documented here:
* https://developers.google.com/youtube/iframe_api_reference#loadVideoByUrl
*
* @param videoURL URL of a YouTube video to load and play.
* @param startSeconds Time in seconds to start the video when it has loaded.
* @param suggestedQuality WKYTPlaybackQuality value suggesting a playback quality.
*/
- (void)loadVideoByURL:(nonnull NSString *)videoURL
startSeconds:(float)startSeconds
suggestedQuality:(WKYTPlaybackQuality)suggestedQuality;
/**
* Loads a given video by its video ID for playback starting and ending at the given times
* with the suggested quality. Loading a video both loads it and begins playback. This method
* corresponds with its JavaScript API equivalent as documented here:
* https://developers.google.com/youtube/iframe_api_reference#loadVideoByUrl
*
* @param videoURL URL of a YouTube video to load and play.
* @param startSeconds Time in seconds to start the video when it has loaded.
* @param endSeconds Time in seconds to end the video after it begins playing.
* @param suggestedQuality WKYTPlaybackQuality value suggesting a playback quality.
*/
- (void)loadVideoByURL:(nonnull NSString *)videoURL
startSeconds:(float)startSeconds
endSeconds:(float)endSeconds
suggestedQuality:(WKYTPlaybackQuality)suggestedQuality;
#pragma mark - Queuing functions for playlists
// Queueing functions for playlists. These methods correspond to
// the JavaScript methods defined here:
// https://developers.google.com/youtube/js_api_reference#Playlist_Queueing_Functions
/**
* Cues a given playlist with the given ID. The |index| parameter specifies the 0-indexed
* position of the first video to play, starting at the given time and with the
* suggested quality. Cueing loads a playlist, but does not start video playback. This method
* corresponds with its JavaScript API equivalent as documented here:
* https://developers.google.com/youtube/iframe_api_reference#cuePlaylist
*
* @param playlistId Playlist ID of a YouTube playlist to cue.
* @param index A 0-indexed position specifying the first video to play.
* @param startSeconds Time in seconds to start the video when WKYTPlayerView::playVideo is called.
* @param suggestedQuality WKYTPlaybackQuality value suggesting a playback quality.
*/
- (void)cuePlaylistByPlaylistId:(nonnull NSString *)playlistId
index:(int)index
startSeconds:(float)startSeconds
suggestedQuality:(WKYTPlaybackQuality)suggestedQuality;
/**
* Cues a playlist of videos with the given video IDs. The |index| parameter specifies the
* 0-indexed position of the first video to play, starting at the given time and with the
* suggested quality. Cueing loads a playlist, but does not start video playback. This method
* corresponds with its JavaScript API equivalent as documented here:
* https://developers.google.com/youtube/iframe_api_reference#cuePlaylist
*
* @param videoIds An NSArray of video IDs to compose the playlist of.
* @param index A 0-indexed position specifying the first video to play.
* @param startSeconds Time in seconds to start the video when WKYTPlayerView::playVideo is called.
* @param suggestedQuality WKYTPlaybackQuality value suggesting a playback quality.
*/
- (void)cuePlaylistByVideos:(nonnull NSArray *)videoIds
index:(int)index
startSeconds:(float)startSeconds
suggestedQuality:(WKYTPlaybackQuality)suggestedQuality;
/**
* Loads a given playlist with the given ID. The |index| parameter specifies the 0-indexed
* position of the first video to play, starting at the given time and with the
* suggested quality. Loading a playlist starts video playback. This method
* corresponds with its JavaScript API equivalent as documented here:
* https://developers.google.com/youtube/iframe_api_reference#loadPlaylist
*
* @param playlistId Playlist ID of a YouTube playlist to cue.
* @param index A 0-indexed position specifying the first video to play.
* @param startSeconds Time in seconds to start the video when WKYTPlayerView::playVideo is called.
* @param suggestedQuality WKYTPlaybackQuality value suggesting a playback quality.
*/
- (void)loadPlaylistByPlaylistId:(nonnull NSString *)playlistId
index:(int)index
startSeconds:(float)startSeconds
suggestedQuality:(WKYTPlaybackQuality)suggestedQuality;
/**
* Loads a playlist of videos with the given video IDs. The |index| parameter specifies the
* 0-indexed position of the first video to play, starting at the given time and with the
* suggested quality. Loading a playlist starts video playback. This method
* corresponds with its JavaScript API equivalent as documented here:
* https://developers.google.com/youtube/iframe_api_reference#loadPlaylist
*
* @param videoIds An NSArray of video IDs to compose the playlist of.
* @param index A 0-indexed position specifying the first video to play.
* @param startSeconds Time in seconds to start the video when WKYTPlayerView::playVideo is called.
* @param suggestedQuality WKYTPlaybackQuality value suggesting a playback quality.
*/
- (void)loadPlaylistByVideos:(nonnull NSArray *)videoIds
index:(int)index
startSeconds:(float)startSeconds
suggestedQuality:(WKYTPlaybackQuality)suggestedQuality;
#pragma mark - Playing a video in a playlist
// These methods correspond to the JavaScript API as defined under the
// "Playing a video in a playlist" section here:
// https://developers.google.com/youtube/iframe_api_reference#Playback_status
/**
* Loads and plays the next video in the playlist. Corresponds to this method from
* the JavaScript API:
* https://developers.google.com/youtube/iframe_api_reference#nextVideo
*/
- (void)nextVideo;
/**
* Loads and plays the previous video in the playlist. Corresponds to this method from
* the JavaScript API:
* https://developers.google.com/youtube/iframe_api_reference#previousVideo
*/
- (void)previousVideo;
/**
* Loads and plays the video at the given 0-indexed position in the playlist.
* Corresponds to this method from the JavaScript API:
* https://developers.google.com/youtube/iframe_api_reference#playVideoAt
*
* @param index The 0-indexed position of the video in the playlist to load and play.
*/
- (void)playVideoAt:(int)index;
#pragma mark - Setting the playback rate
/**
* Gets the playback rate. The default value is 1.0, which represents a video
* playing at normal speed. Other values may include 0.25 or 0.5 for slower
* speeds, and 1.5 or 2.0 for faster speeds. This method corresponds to the
* JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate
*/
- (void)getPlaybackRate:(void (^ __nullable)(float playbackRate, NSError * __nullable error))completionHandler;
/**
* Sets the playback rate. The default value is 1.0, which represents a video
* playing at normal speed. Other values may include 0.25 or 0.5 for slower
* speeds, and 1.5 or 2.0 for faster speeds. To fetch a list of valid values for
* this method, call WKYTPlayerView::getAvailablePlaybackRates. This method does not
* guarantee that the playback rate will change.
* This method corresponds to the JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate
*
* @param suggestedRate A playback rate to suggest for the player.
*/
- (void)setPlaybackRate:(float)suggestedRate;
/**
* Gets a list of the valid playback rates, useful in conjunction with
* WKYTPlayerView::setPlaybackRate. This method corresponds to the
* JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate
*/
- (void)getAvailablePlaybackRates:(void (^ __nullable)(NSArray * __nullable availablePlaybackRates, NSError * __nullable error))completionHandler;
#pragma mark - Setting playback behavior for playlists
/**
* Sets whether the player should loop back to the first video in the playlist
* after it has finished playing the last video. This method corresponds to the
* JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#loopPlaylist
*
* @param loop A boolean representing whether the player should loop.
*/
- (void)setLoop:(BOOL)loop;
/**
* Sets whether the player should shuffle through the playlist. This method
* corresponds to the JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#shufflePlaylist
*
* @param shuffle A boolean representing whether the player should
* shuffle through the playlist.
*/
- (void)setShuffle:(BOOL)shuffle;
#pragma mark - Playback status
// These methods correspond to the JavaScript methods defined here:
// https://developers.google.com/youtube/js_api_reference#Playback_status
/**
* Returns a number between 0 and 1 that specifies the percentage of the video
* that the player shows as buffered. This method corresponds to the
* JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getVideoLoadedFraction
*
*/
- (void)getVideoLoadedFraction:(void (^ __nullable)(float videoLoadedFraction, NSError * __nullable error))completionHandler;
/**
* Returns the state of the player. This method corresponds to the
* JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getPlayerState
*
*/
- (void)getPlayerState:(void (^ __nullable)(WKYTPlayerState playerState, NSError * __nullable error))completionHandler;
/**
* Returns the elapsed time in seconds since the video started playing. This
* method corresponds to the JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getCurrentTime
*
*/
- (void)getCurrentTime:(void (^ __nullable)(float currentTime, NSError * __nullable error))completionHandler;
#pragma mark - Playback quality
// Playback quality. These methods correspond to the JavaScript
// methods defined here:
// https://developers.google.com/youtube/js_api_reference#Playback_quality
/**
* Returns the playback quality. This method corresponds to the
* JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getPlaybackQuality
*
*/
- (void)getPlaybackQuality:(void (^ __nullable)(WKYTPlaybackQuality playbackQuality, NSError * __nullable error))completionHandler;
/**
* Suggests playback quality for the video. It is recommended to leave this setting to
* |default|. This method corresponds to the JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#setPlaybackQuality
*
*/
- (void)setPlaybackQuality:(WKYTPlaybackQuality)suggestedQuality;
/**
* Gets a list of the valid playback quality values, useful in conjunction with
* WKYTPlayerView::setPlaybackQuality. This method corresponds to the
* JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getAvailableQualityLevels
*
*/
- (void)getAvailableQualityLevels:(void (^ __nullable)(NSArray * __nullable availableQualityLevels, NSError * __nullable error))completionHandler;
#pragma mark - Retrieving video information
// Retrieving video information. These methods correspond to the JavaScript
// methods defined here:
// https://developers.google.com/youtube/js_api_reference#Retrieving_video_information
/**
* Returns the duration in seconds since the video of the video. This
* method corresponds to the JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getDuration
*
*/
- (void)getDuration:(void (^ __nullable)(NSTimeInterval duration, NSError * __nullable error))completionHandler;
/**
* Returns the YouTube.com URL for the video. This method corresponds
* to the JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getVideoUrl
*
*/
- (void)getVideoUrl:(void (^ __nullable)(NSURL * __nullable videoUrl, NSError * __nullable error))completionHandler;
/**
* Returns the embed code for the current video. This method corresponds
* to the JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getVideoEmbedCode
*
*/
- (void)getVideoEmbedCode:(void (^ __nullable)(NSString * __nullable videoEmbedCode, NSError * __nullable error))completionHandler;
#pragma mark - Retrieving playlist information
// Retrieving playlist information. These methods correspond to the
// JavaScript defined here:
// https://developers.google.com/youtube/js_api_reference#Retrieving_playlist_information
/**
* Returns an ordered array of video IDs in the playlist. This method corresponds
* to the JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getPlaylist
*
*/
- (void)getPlaylist:(void (^ __nullable)(NSArray * __nullable playlist, NSError * __nullable error))completionHandler;
/**
* Returns the 0-based index of the currently playing item in the playlist.
* This method corresponds to the JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getPlaylistIndex
*
*/
- (void)getPlaylistIndex:(void (^ __nullable)(int playlistIndex, NSError * __nullable error))completionHandler;
#pragma mark - Mute
/**
* Mutes the player. Corresponds to this method from
* the JavaScript API:
* https://developers.google.com/youtube/iframe_api_reference#mute
*/
- (void)mute;
/**
* Unmutes the player. Corresponds to this method from
* the JavaScript API:
* https://developers.google.com/youtube/iframe_api_reference#unMute
*/
- (void)unMute;
/**
* Returns true if the player is muted, false if not.
* This method corresponds to the JavaScript API defined here:
* https://developers.google.com/youtube/iframe_api_reference#getVolume
*
*/
- (void)isMuted:(void (^ __nullable)(BOOL isMuted, NSError * __nullable error))completionHandler;
#pragma mark - Exposed for Testing
/**
* Removes the internal web view from this player view.
* Intended to use for testing, should not be used in production code.
*/
- (void)removeWebView;
@end

File diff suppressed because it is too large Load diff

View file

@ -40,6 +40,7 @@ const config = {
};
},
},
maxWorkers: 4,
};
module.exports = config;