diff --git a/.circleci/config.yml b/.circleci/config.yml index c44a1c76f..64adeca88 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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: <> + + 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:../<> + + 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:../<> + + 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/<> 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-.*/ diff --git a/.gitignore b/.gitignore index e8c5ae3c7..bd5cbe33e 100644 --- a/.gitignore +++ b/.gitignore @@ -85,10 +85,6 @@ ios/sentry.properties .nyc_output coverage -# Pods -.podinstall -ios/Pods/ - # Bundle artifact *.jsbundle diff --git a/Makefile b/Makefile index 4c3178a3f..8e3c9ab1d 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 329082953..301ec13dc 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -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 diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeClient.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeClient.h new file mode 120000 index 000000000..2bd449376 --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeClient.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeClient.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeDashManifestXML.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeDashManifestXML.h new file mode 120000 index 000000000..02be59fee --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeDashManifestXML.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeDashManifestXML.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeError.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeError.h new file mode 120000 index 000000000..4c6252175 --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeError.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeError.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeKit.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeKit.h new file mode 120000 index 000000000..d16f6f871 --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeKit.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeKit.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeLogger+Private.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeLogger+Private.h new file mode 120000 index 000000000..df0c2ebce --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeLogger+Private.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger+Private.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeLogger.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeLogger.h new file mode 120000 index 000000000..0d47450a6 --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeLogger.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeOperation.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeOperation.h new file mode 120000 index 000000000..e8d6b951c --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeOperation.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeOperation.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubePlayerScript.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubePlayerScript.h new file mode 120000 index 000000000..caeaae4a1 --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubePlayerScript.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubePlayerScript.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideo+Private.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideo+Private.h new file mode 120000 index 000000000..e8344312d --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideo+Private.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo+Private.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideo.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideo.h new file mode 120000 index 000000000..ee13de8b6 --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideo.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideoOperation.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideoOperation.h new file mode 120000 index 000000000..ac0730e71 --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideoOperation.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoOperation.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h new file mode 120000 index 000000000..0ac32058e --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideoWebpage.h b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideoWebpage.h new file mode 120000 index 000000000..4c0f3d23d --- /dev/null +++ b/ios/Pods/Headers/Private/XCDYouTubeKit/XCDYouTubeVideoWebpage.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoWebpage.h \ No newline at end of file diff --git a/ios/Pods/Headers/Private/YoutubePlayer-in-WKWebView/WKYTPlayerView.h b/ios/Pods/Headers/Private/YoutubePlayer-in-WKWebView/WKYTPlayerView.h new file mode 120000 index 000000000..f174706c0 --- /dev/null +++ b/ios/Pods/Headers/Private/YoutubePlayer-in-WKWebView/WKYTPlayerView.h @@ -0,0 +1 @@ +../../../YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.h \ No newline at end of file diff --git a/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeClient.h b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeClient.h new file mode 120000 index 000000000..2bd449376 --- /dev/null +++ b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeClient.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeClient.h \ No newline at end of file diff --git a/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeError.h b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeError.h new file mode 120000 index 000000000..4c6252175 --- /dev/null +++ b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeError.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeError.h \ No newline at end of file diff --git a/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeKit.h b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeKit.h new file mode 120000 index 000000000..d16f6f871 --- /dev/null +++ b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeKit.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeKit.h \ No newline at end of file diff --git a/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeLogger.h b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeLogger.h new file mode 120000 index 000000000..0d47450a6 --- /dev/null +++ b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeLogger.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger.h \ No newline at end of file diff --git a/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeOperation.h b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeOperation.h new file mode 120000 index 000000000..e8d6b951c --- /dev/null +++ b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeOperation.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeOperation.h \ No newline at end of file diff --git a/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeVideo.h b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeVideo.h new file mode 120000 index 000000000..ee13de8b6 --- /dev/null +++ b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeVideo.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo.h \ No newline at end of file diff --git a/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeVideoOperation.h b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeVideoOperation.h new file mode 120000 index 000000000..ac0730e71 --- /dev/null +++ b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeVideoOperation.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoOperation.h \ No newline at end of file diff --git a/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h new file mode 120000 index 000000000..0ac32058e --- /dev/null +++ b/ios/Pods/Headers/Public/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h @@ -0,0 +1 @@ +../../../XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h \ No newline at end of file diff --git a/ios/Pods/Headers/Public/YoutubePlayer-in-WKWebView/WKYTPlayerView.h b/ios/Pods/Headers/Public/YoutubePlayer-in-WKWebView/WKYTPlayerView.h new file mode 120000 index 000000000..f174706c0 --- /dev/null +++ b/ios/Pods/Headers/Public/YoutubePlayer-in-WKWebView/WKYTPlayerView.h @@ -0,0 +1 @@ +../../../YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.h \ No newline at end of file diff --git a/ios/Pods/Manifest.lock b/ios/Pods/Manifest.lock new file mode 100644 index 000000000..dd0e6c594 --- /dev/null +++ b/ios/Pods/Manifest.lock @@ -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 diff --git a/ios/Pods/Pods.xcodeproj/project.pbxproj b/ios/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 000000000..be530aa9c --- /dev/null +++ b/ios/Pods/Pods.xcodeproj/project.pbxproj @@ -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 = ""; }; + 0AD2A25648B085555683131216BAD797 /* Pods-Mattermost-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-Mattermost-acknowledgements.markdown"; sourceTree = ""; }; + 0CA88A7462704DBCB53C017185C1A65F /* Pods-Mattermost-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-Mattermost-acknowledgements.plist"; sourceTree = ""; }; + 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 = ""; }; + 18BED515670B4F1D15806832C57D59F9 /* XCDYouTubeDashManifestXML.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeDashManifestXML.h; path = XCDYouTubeKit/XCDYouTubeDashManifestXML.h; sourceTree = ""; }; + 19281C3EF03E987F37C203D17BCA2EC5 /* WKYTPlayerView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = WKYTPlayerView.h; path = WKYTPlayerView/WKYTPlayerView.h; sourceTree = ""; }; + 1CBB4B14F0B800EC1527A5353A3AE1CA /* XCDYouTubeVideo+Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "XCDYouTubeVideo+Private.h"; path = "XCDYouTubeKit/XCDYouTubeVideo+Private.h"; sourceTree = ""; }; + 218BAF448A4CEBF30D9938922CA236C2 /* XCDYouTubeClient.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeClient.h; path = XCDYouTubeKit/XCDYouTubeClient.h; sourceTree = ""; }; + 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 = ""; }; + 294543F53C1E8CC7BECE8214C09765EE /* XCDYouTubeVideoPlayerViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeVideoPlayerViewController.h; path = XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h; sourceTree = ""; }; + 377B2C391CA94B7612EDC96C2306DB48 /* Pods-MattermostTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-MattermostTests.release.xcconfig"; sourceTree = ""; }; + 3BC2AF7C034FA52EAB440AC387B0F3E9 /* XCDYouTubeKit.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = XCDYouTubeKit.xcconfig; sourceTree = ""; }; + 3DAF995C826738DB2D85603716D91154 /* XCDYouTubeLogger+Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "XCDYouTubeLogger+Private.h"; path = "XCDYouTubeKit/XCDYouTubeLogger+Private.h"; sourceTree = ""; }; + 46714161B2C62428DB972729952DC352 /* XCDYouTubeClient.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubeClient.m; path = XCDYouTubeKit/XCDYouTubeClient.m; sourceTree = ""; }; + 510EF6F5EB43790577352F6C8A0718B0 /* XCDYouTubeLogger.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeLogger.h; path = XCDYouTubeKit/XCDYouTubeLogger.h; sourceTree = ""; }; + 60FB83EAEF578DD0C2DE316FCF4322A9 /* Pods-MattermostTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-MattermostTests.debug.xcconfig"; sourceTree = ""; }; + 6170F2E79BA4DE1619C6FD1F25C3901F /* Pods-Mattermost-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-Mattermost-frameworks.sh"; sourceTree = ""; }; + 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 = ""; }; + 72206B26A17824A476D334C5612D4142 /* YoutubePlayer-in-WKWebView-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "YoutubePlayer-in-WKWebView-dummy.m"; sourceTree = ""; }; + 724042289C9DC57B313DD893EFD1AA54 /* XCDYouTubeKit.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeKit.h; path = XCDYouTubeKit/XCDYouTubeKit.h; sourceTree = ""; }; + 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 = ""; }; + 8540AD01026C33CA77834BD0EE077CDB /* XCDYouTubeVideoWebpage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeVideoWebpage.h; path = XCDYouTubeKit/XCDYouTubeVideoWebpage.h; sourceTree = ""; }; + 8A89EF129E834187D884270CF5CAF3C5 /* Pods-Mattermost-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-Mattermost-resources.sh"; sourceTree = ""; }; + 8E94B5BEED415E94C72301A86E7A9859 /* XCDYouTubePlayerScript.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubePlayerScript.m; path = XCDYouTubeKit/XCDYouTubePlayerScript.m; sourceTree = ""; }; + 920F9C3CEECF4C1924F458E3D0428ED8 /* XCDYouTubeVideoWebpage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubeVideoWebpage.m; path = XCDYouTubeKit/XCDYouTubeVideoWebpage.m; sourceTree = ""; }; + 926D56CC36F536DEFF1A45331E070750 /* Pods-Mattermost-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-Mattermost-dummy.m"; sourceTree = ""; }; + 934A4B9575F4CBDCBE7B044C1F049365 /* Pods-Mattermost.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Mattermost.debug.xcconfig"; sourceTree = ""; }; + 974866440990F0A14694BD9B2E9962E0 /* Pods-MattermostTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-MattermostTests-acknowledgements.markdown"; sourceTree = ""; }; + 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 = ""; }; + A16570331366E84C38260E575C1ACDAF /* XCDYouTubeOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeOperation.h; path = XCDYouTubeKit/XCDYouTubeOperation.h; sourceTree = ""; }; + A43B4ECF27212BC0465EDA19AE3164CC /* XCDYouTubeKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCDYouTubeKit-prefix.pch"; sourceTree = ""; }; + B564ED8191C54D3CAD8D2A2A6B9B9D1B /* Pods-Mattermost.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Mattermost.release.xcconfig"; sourceTree = ""; }; + BA781F6AB0F7487AD05BC63EA40AB8BF /* XCDYouTubeVideoOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubeVideoOperation.m; path = XCDYouTubeKit/XCDYouTubeVideoOperation.m; sourceTree = ""; }; + BB52596A2D243BBDCE8BCB3CF50A6E20 /* XCDYouTubeVideo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubeVideo.h; path = XCDYouTubeKit/XCDYouTubeVideo.h; sourceTree = ""; }; + C0A3E27A51ED227A8460BDBC0B1A5BDC /* Pods-MattermostTests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-MattermostTests-dummy.m"; sourceTree = ""; }; + C70E5870375DC970207B78465E560E64 /* YoutubePlayer-in-WKWebView-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "YoutubePlayer-in-WKWebView-prefix.pch"; sourceTree = ""; }; + 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 = ""; }; + CFFE5759AD00493BC5172561B1C1C7CD /* XCDYouTubeKit-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "XCDYouTubeKit-dummy.m"; sourceTree = ""; }; + DD5E806A91AFE8DD2B077AE695123547 /* XCDYouTubeVideoPlayerViewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XCDYouTubeVideoPlayerViewController.m; path = XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.m; sourceTree = ""; }; + E0545ADDF3E1FF7C1BC19F497787C42B /* WKYTPlayerView.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; name = WKYTPlayerView.bundle; path = WKYTPlayerView/WKYTPlayerView.bundle; sourceTree = ""; }; + E4748ACD84B23087E6F590E0904FFEFB /* YoutubePlayer-in-WKWebView.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "YoutubePlayer-in-WKWebView.xcconfig"; sourceTree = ""; }; + 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 = ""; }; + F1766C4F2FDC063F421CA2D4688FED68 /* XCDYouTubePlayerScript.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XCDYouTubePlayerScript.h; path = XCDYouTubeKit/XCDYouTubePlayerScript.h; sourceTree = ""; }; + FFD8DC263483FE0689017EF3573C5C71 /* Pods-MattermostTests-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-MattermostTests-frameworks.sh"; sourceTree = ""; }; +/* 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 = ""; + }; + 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 = ""; + }; + 428BC12C5AD7D04CCD5564A473A61275 /* Pods */ = { + isa = PBXGroup; + children = ( + 950725B3833769C44391B7E733E759D0 /* XCDYouTubeKit */, + 6571058FEA94E8172ABEAFD4A1D3C1A7 /* YoutubePlayer-in-WKWebView */, + ); + name = Pods; + sourceTree = ""; + }; + 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 = ""; + }; + 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 = ""; + }; + 7D3B7C9338829DEF8BC3A52614FF35D6 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 3EAAB2CFFBD8598AB3144F85D9EDACD8 /* Pods-Mattermost */, + 6CDFB0049112FA789537AC9B9E94FC6E /* Pods-MattermostTests */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + 92B16AFE4649769630669C6CDA9910C9 /* Resources */ = { + isa = PBXGroup; + children = ( + E0545ADDF3E1FF7C1BC19F497787C42B /* WKYTPlayerView.bundle */, + ); + name = Resources; + sourceTree = ""; + }; + 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 = ""; + }; + A3D27D0A8AF193B0B5A20E3F139F18EE /* Products */ = { + isa = PBXGroup; + children = ( + 7703139D81F98B8B6130B2DAFB831C41 /* libPods-Mattermost.a */, + E99B330F0F9A45FF2E1ACB80DA268841 /* libPods-MattermostTests.a */, + C99CF8DC55235B8BA857A47235948EE7 /* libXCDYouTubeKit.a */, + 6CD50F1ED20084866B7CE47B8F8E4BE4 /* libYoutubePlayer-in-WKWebView.a */, + ); + name = Products; + sourceTree = ""; + }; + B01816E2D09E8070AEE9E96136D917B6 /* iOS */ = { + isa = PBXGroup; + children = ( + 98C181471820795EDEAACBC10B84709A /* JavaScriptCore.framework */, + 15D48C716A411AD7621E7125E083F54A /* MediaPlayer.framework */, + ); + name = iOS; + sourceTree = ""; + }; + 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 = ""; + }; + CF1408CF629C7361332E53B88F7BD30C = { + isa = PBXGroup; + children = ( + 24390EFD555DD124430DFF9724065945 /* Podfile */, + 1628BF05B4CAFDCC3549A101F5A10A17 /* Frameworks */, + 428BC12C5AD7D04CCD5564A473A61275 /* Pods */, + A3D27D0A8AF193B0B5A20E3F139F18EE /* Products */, + 7D3B7C9338829DEF8BC3A52614FF35D6 /* Targets Support Files */, + ); + sourceTree = ""; + }; + 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 = ""; + }; +/* 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 */; +} diff --git a/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-acknowledgements.markdown b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-acknowledgements.markdown new file mode 100644 index 000000000..8f762b302 --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-acknowledgements.markdown @@ -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 diff --git a/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-acknowledgements.plist b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-acknowledgements.plist new file mode 100644 index 000000000..5935b6519 --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-acknowledgements.plist @@ -0,0 +1,84 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + 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. + + + License + MIT + Title + XCDYouTubeKit + Type + PSGroupSpecifier + + + FooterText + 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. + + License + Apache + Title + YoutubePlayer-in-WKWebView + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-dummy.m b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-dummy.m new file mode 100644 index 000000000..8ec219786 --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_Mattermost : NSObject +@end +@implementation PodsDummy_Pods_Mattermost +@end diff --git a/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-frameworks.sh b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-frameworks.sh new file mode 100755 index 000000000..08e3eaaca --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-frameworks.sh @@ -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 diff --git a/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh new file mode 100755 index 000000000..40b59bd7a --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh @@ -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 diff --git a/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost.debug.xcconfig b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost.debug.xcconfig new file mode 100644 index 000000000..eb4f27eb5 --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost.debug.xcconfig @@ -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 diff --git a/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost.release.xcconfig b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost.release.xcconfig new file mode 100644 index 000000000..eb4f27eb5 --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost.release.xcconfig @@ -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 diff --git a/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-acknowledgements.markdown b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-acknowledgements.markdown new file mode 100644 index 000000000..102af7538 --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-acknowledgements.markdown @@ -0,0 +1,3 @@ +# Acknowledgements +This application makes use of the following third party libraries: +Generated by CocoaPods - https://cocoapods.org diff --git a/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-acknowledgements.plist b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-acknowledgements.plist new file mode 100644 index 000000000..7acbad1ea --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-acknowledgements.plist @@ -0,0 +1,29 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-dummy.m b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-dummy.m new file mode 100644 index 000000000..a018bb5d5 --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_MattermostTests : NSObject +@end +@implementation PodsDummy_Pods_MattermostTests +@end diff --git a/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-frameworks.sh b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-frameworks.sh new file mode 100755 index 000000000..08e3eaaca --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-frameworks.sh @@ -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 diff --git a/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-resources.sh b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-resources.sh new file mode 100755 index 000000000..345301f2c --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests-resources.sh @@ -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 diff --git a/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests.debug.xcconfig b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests.debug.xcconfig new file mode 100644 index 000000000..eb5fde866 --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests.debug.xcconfig @@ -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 diff --git a/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests.release.xcconfig b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests.release.xcconfig new file mode 100644 index 000000000..eb5fde866 --- /dev/null +++ b/ios/Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests.release.xcconfig @@ -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 diff --git a/ios/Pods/Target Support Files/XCDYouTubeKit/XCDYouTubeKit-dummy.m b/ios/Pods/Target Support Files/XCDYouTubeKit/XCDYouTubeKit-dummy.m new file mode 100644 index 000000000..218334986 --- /dev/null +++ b/ios/Pods/Target Support Files/XCDYouTubeKit/XCDYouTubeKit-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_XCDYouTubeKit : NSObject +@end +@implementation PodsDummy_XCDYouTubeKit +@end diff --git a/ios/Pods/Target Support Files/XCDYouTubeKit/XCDYouTubeKit-prefix.pch b/ios/Pods/Target Support Files/XCDYouTubeKit/XCDYouTubeKit-prefix.pch new file mode 100644 index 000000000..beb2a2441 --- /dev/null +++ b/ios/Pods/Target Support Files/XCDYouTubeKit/XCDYouTubeKit-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/ios/Pods/Target Support Files/XCDYouTubeKit/XCDYouTubeKit.xcconfig b/ios/Pods/Target Support Files/XCDYouTubeKit/XCDYouTubeKit.xcconfig new file mode 100644 index 000000000..af4d7a935 --- /dev/null +++ b/ios/Pods/Target Support Files/XCDYouTubeKit/XCDYouTubeKit.xcconfig @@ -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 diff --git a/ios/Pods/Target Support Files/YoutubePlayer-in-WKWebView/YoutubePlayer-in-WKWebView-dummy.m b/ios/Pods/Target Support Files/YoutubePlayer-in-WKWebView/YoutubePlayer-in-WKWebView-dummy.m new file mode 100644 index 000000000..a5085282e --- /dev/null +++ b/ios/Pods/Target Support Files/YoutubePlayer-in-WKWebView/YoutubePlayer-in-WKWebView-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_YoutubePlayer_in_WKWebView : NSObject +@end +@implementation PodsDummy_YoutubePlayer_in_WKWebView +@end diff --git a/ios/Pods/Target Support Files/YoutubePlayer-in-WKWebView/YoutubePlayer-in-WKWebView-prefix.pch b/ios/Pods/Target Support Files/YoutubePlayer-in-WKWebView/YoutubePlayer-in-WKWebView-prefix.pch new file mode 100644 index 000000000..beb2a2441 --- /dev/null +++ b/ios/Pods/Target Support Files/YoutubePlayer-in-WKWebView/YoutubePlayer-in-WKWebView-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/ios/Pods/Target Support Files/YoutubePlayer-in-WKWebView/YoutubePlayer-in-WKWebView.xcconfig b/ios/Pods/Target Support Files/YoutubePlayer-in-WKWebView/YoutubePlayer-in-WKWebView.xcconfig new file mode 100644 index 000000000..ac30dae33 --- /dev/null +++ b/ios/Pods/Target Support Files/YoutubePlayer-in-WKWebView/YoutubePlayer-in-WKWebView.xcconfig @@ -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 diff --git a/ios/Pods/XCDYouTubeKit/LICENSE b/ios/Pods/XCDYouTubeKit/LICENSE new file mode 100644 index 000000000..e21050fe2 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/LICENSE @@ -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. + diff --git a/ios/Pods/XCDYouTubeKit/README.md b/ios/Pods/XCDYouTubeKit/README.md new file mode 100644 index 000000000..71f688c3a --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/README.md @@ -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. + + + +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 app’s target. + +These steps will ensure that `#import ` 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. diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeClient.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeClient.h new file mode 100644 index 000000000..2b89201c3 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeClient.h @@ -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 + +#import +#import +#import + +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 don’t want to use `XCDYouTubeClient` directly but the higher level class ``. + */ +@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 `` 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 `` 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) 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 `` 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 `` 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) getVideoWithIdentifier:(NSString *)videoIdentifier cookies:(nullable NSArray *)cookies completionHandler:(void (^)(XCDYouTubeVideo * __nullable video, NSError * __nullable error))completionHandler; +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeClient.m b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeClient.m new file mode 100644 index 000000000..95ff0053c --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeClient.m @@ -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) getVideoWithIdentifier:(NSString *)videoIdentifier cookies:(NSArray *)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) getVideoWithIdentifier:(NSString *)videoIdentifier completionHandler:(void (^)(XCDYouTubeVideo * __nullable video, NSError * __nullable error))completionHandler +{ + return [self getVideoWithIdentifier:videoIdentifier cookies:nil completionHandler:completionHandler]; +} + +@end diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeDashManifestXML.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeDashManifestXML.h new file mode 100644 index 000000000..90658ecb0 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeDashManifestXML.h @@ -0,0 +1,17 @@ +// +// XCDYouTubeDashManifestXML.h +// XCDYouTubeKit +// +// Created by Soneé John on 10/24/17. +// Copyright © 2017 Cédric Luthi. All rights reserved. +// + +#import +__attribute__((visibility("hidden"))) +@interface XCDYouTubeDashManifestXML : NSObject + +- (instancetype)initWithXMLString:(NSString *)XMLString; + +- (NSDictionary *)streamURLs; + +@end diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeDashManifestXML.m b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeDashManifestXML.m new file mode 100644 index 000000000..ea7565645 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeDashManifestXML.m @@ -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:@"(?<=())(\\w|\\d|\\n|[().,\\-:;@#$%^&*\\[\\]\"'+–/\\/®°⁰!?{}|`~]| )+?(?=())" 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 *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 diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeError.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeError.h new file mode 100644 index 000000000..92ffe38e5 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeError.h @@ -0,0 +1,47 @@ +// +// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. +// + +#import + +/** + * 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 +}; diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeKit.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeKit.h new file mode 100644 index 000000000..250ef88f2 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeKit.h @@ -0,0 +1,16 @@ +// +// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import +#import + +#if TARGET_OS_IOS || (!defined(TARGET_OS_IOS) && TARGET_OS_IPHONE) +#import +#endif diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger+Private.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger+Private.h new file mode 100644 index 000000000..65612410a --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger+Private.h @@ -0,0 +1,21 @@ +// +// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. +// + +#import + +#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__]; })) diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger.h new file mode 100644 index 000000000..b5737ee6e --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger.h @@ -0,0 +1,103 @@ +// +// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. +// + +#import + +/** + * 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 `` 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 `` starts, is cancelled or finishes. + */ + XCDLogLevelInfo = 2, + + /** + * Used throughout a `` 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 `` 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 ``. + * - 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 diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger.m b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger.m new file mode 100644 index 000000000..511e58c39 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeLogger.m @@ -0,0 +1,63 @@ +// +// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. +// + +#import "XCDYouTubeLogger.h" + +#import + +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 diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeOperation.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeOperation.h new file mode 100644 index 000000000..0b425c4df --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeOperation.h @@ -0,0 +1,23 @@ +// +// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. +// + +#import + +/** + * The `XCDYouTubeOperation` protocol is adopted by opaque objects returned by the `<-[XCDYouTubeClient getVideoWithIdentifier:completionHandler:]>` method. + */ +@protocol XCDYouTubeOperation + +/** + * --------------- + * @name Canceling + * --------------- + */ + +/** + * Cancels the operation. If the operation is already finished, does nothing. + */ +- (void) cancel; + +@end diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubePlayerScript.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubePlayerScript.h new file mode 100644 index 000000000..5421a1cc6 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubePlayerScript.h @@ -0,0 +1,14 @@ +// +// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. +// + +#import + +__attribute__((visibility("hidden"))) +@interface XCDYouTubePlayerScript : NSObject + +- (instancetype) initWithString:(NSString *)string; + +- (NSString *) unscrambleSignature:(NSString *)scrambledSignature; + +@end diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubePlayerScript.m b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubePlayerScript.m new file mode 100644 index 000000000..c40209d27 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubePlayerScript.m @@ -0,0 +1,125 @@ +// +// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. +// + +#import "XCDYouTubePlayerScript.h" + +#import + +#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*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*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 *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 diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo+Private.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo+Private.h new file mode 100644 index 000000000..9f751206b --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo+Private.h @@ -0,0 +1,24 @@ +// +// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. +// + +#import + +#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 diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo.h new file mode 100644 index 000000000..7668a9301 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo.h @@ -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 + +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 | 3–5.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 + +/** + * -------------------------------- + * @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 *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 *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 *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 diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo.m b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo.m new file mode 100644 index 000000000..c7ae55f0f --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideo.m @@ -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 + +NSString *const XCDYouTubeVideoErrorDomain = @"XCDYouTubeVideoErrorDomain"; +NSString *const XCDYouTubeAllowedCountriesUserInfoKey = @"AllowedCountries"; +NSString *const XCDYouTubeNoStreamVideoUserInfoKey = @"NoStreamVideo"; +NSString *const XCDYouTubeVideoQualityHTTPLiveStreaming = @"HTTPLiveStreaming"; + +NSArray *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:@"" 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 diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoOperation.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoOperation.h new file mode 100644 index 000000000..4ee8b7b06 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoOperation.h @@ -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 + +#import +#import + +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 ``. Use this class only if you are very familiar with `NSOperation` and need to manage dependencies between operations. + */ +@interface XCDYouTubeVideoOperation : NSOperation + +/** + * ------------------ + * @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 *)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 diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoOperation.m b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoOperation.m new file mode 100644 index 000000000..5fa203f46 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoOperation.m @@ -0,0 +1,410 @@ +// +// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. +// + +#import "XCDYouTubeVideoOperation.h" + +#import + +#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 *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 *)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 diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h new file mode 100644 index 000000000..812ee9c15 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.h @@ -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 + +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 UIViewController’s `presentMoviePlayerViewControllerAnimated:` method to play a YouTube video fullscreen. + * + * Use the `` 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 `` 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 `` 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 diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.m b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.m new file mode 100644 index 000000000..ebec6c3c1 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoPlayerViewController.m @@ -0,0 +1,209 @@ +// +// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. +// + +#import "XCDYouTubeVideoPlayerViewController.h" + +#import "XCDYouTubeClient.h" + +#import + +#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 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 diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoWebpage.h b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoWebpage.h new file mode 100644 index 000000000..750cfdc74 --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoWebpage.h @@ -0,0 +1,18 @@ +// +// Copyright (c) 2013-2016 Cédric Luthi. All rights reserved. +// + +#import + +__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 diff --git a/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoWebpage.m b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoWebpage.m new file mode 100644 index 000000000..4e56feb2a --- /dev/null +++ b/ios/Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoWebpage.m @@ -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 diff --git a/ios/Pods/YoutubePlayer-in-WKWebView/LICENSE b/ios/Pods/YoutubePlayer-in-WKWebView/LICENSE new file mode 100644 index 000000000..bfdf05f50 --- /dev/null +++ b/ios/Pods/YoutubePlayer-in-WKWebView/LICENSE @@ -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. \ No newline at end of file diff --git a/ios/Pods/YoutubePlayer-in-WKWebView/README.md b/ios/Pods/YoutubePlayer-in-WKWebView/README.md new file mode 100644 index 000000000..67b80b8f5 --- /dev/null +++ b/ios/Pods/YoutubePlayer-in-WKWebView/README.md @@ -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. diff --git a/ios/Pods/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.bundle/Assets/YTPlayerView-iframe-player.html b/ios/Pods/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.bundle/Assets/YTPlayerView-iframe-player.html new file mode 100644 index 000000000..975510efa --- /dev/null +++ b/ios/Pods/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.bundle/Assets/YTPlayerView-iframe-player.html @@ -0,0 +1,90 @@ + + + + + + + +
+
+
+ + + + diff --git a/ios/Pods/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.h b/ios/Pods/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.h new file mode 100644 index 000000000..d532d1d25 --- /dev/null +++ b/ios/Pods/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.h @@ -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 +#import + +@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 + +@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 + +@property(nonatomic, strong, nullable, readonly) WKWebView *webView; + +/** A delegate to be notified on playback events. */ +@property(nonatomic, weak, nullable) id delegate; + +/** + * This method loads the player with the given video ID. + * This is a convenience method for calling 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 diff --git a/ios/Pods/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.m b/ios/Pods/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.m new file mode 100644 index 000000000..d30b8a9f6 --- /dev/null +++ b/ios/Pods/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.m @@ -0,0 +1,1123 @@ +// 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 "WKYTPlayerView.h" + +// These are instances of NSString because we get them from parsing a URL. It would be silly to +// convert these into an integer just to have to convert the URL query string value into an integer +// as well for the sake of doing a value comparison. A full list of response error codes can be +// found here: +// https://developers.google.com/youtube/iframe_api_reference +NSString static *const kWKYTPlayerStateUnstartedCode = @"-1"; +NSString static *const kWKYTPlayerStateEndedCode = @"0"; +NSString static *const kWKYTPlayerStatePlayingCode = @"1"; +NSString static *const kWKYTPlayerStatePausedCode = @"2"; +NSString static *const kWKYTPlayerStateBufferingCode = @"3"; +NSString static *const kWKYTPlayerStateCuedCode = @"5"; +NSString static *const kWKYTPlayerStateUnknownCode = @"unknown"; + +// Constants representing playback quality. +NSString static *const kWKYTPlaybackQualitySmallQuality = @"small"; +NSString static *const kWKYTPlaybackQualityMediumQuality = @"medium"; +NSString static *const kWKYTPlaybackQualityLargeQuality = @"large"; +NSString static *const kWKYTPlaybackQualityHD720Quality = @"hd720"; +NSString static *const kWKYTPlaybackQualityHD1080Quality = @"hd1080"; +NSString static *const kWKYTPlaybackQualityHighResQuality = @"highres"; +NSString static *const kWKYTPlaybackQualityAutoQuality = @"auto"; +NSString static *const kWKYTPlaybackQualityDefaultQuality = @"default"; +NSString static *const kWKYTPlaybackQualityUnknownQuality = @"unknown"; + +// Constants representing YouTube player errors. +NSString static *const kWKYTPlayerErrorInvalidParamErrorCode = @"2"; +NSString static *const kWKYTPlayerErrorHTML5ErrorCode = @"5"; +NSString static *const kWKYTPlayerErrorVideoNotFoundErrorCode = @"100"; +NSString static *const kWKYTPlayerErrorNotEmbeddableErrorCode = @"101"; +NSString static *const kWKYTPlayerErrorCannotFindVideoErrorCode = @"105"; +NSString static *const kWKYTPlayerErrorSameAsNotEmbeddableErrorCode = @"150"; + +// Constants representing player callbacks. +NSString static *const kWKYTPlayerCallbackOnReady = @"onReady"; +NSString static *const kWKYTPlayerCallbackOnStateChange = @"onStateChange"; +NSString static *const kWKYTPlayerCallbackOnPlaybackQualityChange = @"onPlaybackQualityChange"; +NSString static *const kWKYTPlayerCallbackOnError = @"onError"; +NSString static *const kWKYTPlayerCallbackOnPlayTime = @"onPlayTime"; + +NSString static *const kWKYTPlayerCallbackOnYouTubeIframeAPIReady = @"onYouTubeIframeAPIReady"; +NSString static *const kWKYTPlayerCallbackOnYouTubeIframeAPIFailedToLoad = @"onYouTubeIframeAPIFailedToLoad"; + +NSString static *const kWKYTPlayerEmbedUrlRegexPattern = @"^http(s)://(www.)youtube.com/embed/(.*)$"; +NSString static *const kWKYTPlayerAdUrlRegexPattern = @"^http(s)://pubads.g.doubleclick.net/pagead/conversion/"; +NSString static *const kWKYTPlayerOAuthRegexPattern = @"^http(s)://accounts.google.com/o/oauth2/(.*)$"; +NSString static *const kWKYTPlayerStaticProxyRegexPattern = @"^https://content.googleapis.com/static/proxy.html(.*)$"; +NSString static *const kWKYTPlayerSyndicationRegexPattern = @"^https://tpc.googlesyndication.com/sodar/(.*).html$"; + +@interface WKYTPlayerView() + +@property (nonatomic, strong) NSURL *originURL; +@property (nonatomic, weak) UIView *initialLoadingView; + +@end + +@implementation WKYTPlayerView + +- (BOOL)loadWithVideoId:(NSString *)videoId { + return [self loadWithVideoId:videoId playerVars:nil]; +} + +- (BOOL)loadWithPlaylistId:(NSString *)playlistId { + return [self loadWithPlaylistId:playlistId playerVars:nil]; +} + +- (BOOL)loadWithVideoId:(NSString *)videoId playerVars:(NSDictionary *)playerVars { + if (!playerVars) { + playerVars = @{}; + } + NSDictionary *playerParams = @{ @"videoId" : videoId, @"playerVars" : playerVars }; + return [self loadWithPlayerParams:playerParams]; +} + +- (BOOL)loadWithVideoId:(NSString *)videoId playerVars:(NSDictionary *)playerVars templatePath:(NSString *)path { + if (!playerVars) { + playerVars = @{}; + } + NSMutableDictionary *playerParams = [@{@"videoId" : videoId, @"playerVars" : playerVars} mutableCopy]; + if (path) { + playerParams[@"templatePath"] = path; + } + return [self loadWithPlayerParams:[playerParams copy]]; +} + +- (BOOL)loadWithPlaylistId:(NSString *)playlistId playerVars:(NSDictionary *)playerVars { + + // Mutable copy because we may have been passed an immutable config dictionary. + NSMutableDictionary *tempPlayerVars = [[NSMutableDictionary alloc] init]; + [tempPlayerVars setValue:@"playlist" forKey:@"listType"]; + [tempPlayerVars setValue:playlistId forKey:@"list"]; + if (playerVars) { + [tempPlayerVars addEntriesFromDictionary:playerVars]; + } + + NSDictionary *playerParams = @{ @"playerVars" : tempPlayerVars }; + return [self loadWithPlayerParams:playerParams]; +} + +#pragma mark - Player methods + +- (void)playVideo { + [self stringFromEvaluatingJavaScript:@"player.playVideo();" completionHandler:nil]; +} + +- (void)pauseVideo { + [self notifyDelegateOfYouTubeCallbackUrl:[NSURL URLWithString:[NSString stringWithFormat:@"ytplayer://onStateChange?data=%@", kWKYTPlayerStatePausedCode]]]; + [self stringFromEvaluatingJavaScript:@"player.pauseVideo();" completionHandler:nil]; +} + +- (void)stopVideo { + [self stringFromEvaluatingJavaScript:@"player.stopVideo();" completionHandler:nil]; +} + +- (void)seekToSeconds:(float)seekToSeconds allowSeekAhead:(BOOL)allowSeekAhead { + NSNumber *secondsValue = [NSNumber numberWithFloat:seekToSeconds]; + NSString *allowSeekAheadValue = [self stringForJSBoolean:allowSeekAhead]; + NSString *command = [NSString stringWithFormat:@"player.seekTo(%@, %@);", secondsValue, allowSeekAheadValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +#pragma mark - Cueing methods + +- (void)cueVideoById:(NSString *)videoId + startSeconds:(float)startSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; + NSString *qualityValue = [WKYTPlayerView stringForPlaybackQuality:suggestedQuality]; + NSString *command = [NSString stringWithFormat:@"player.cueVideoById('%@', %@, '%@');", + videoId, startSecondsValue, qualityValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +- (void)cueVideoById:(NSString *)videoId + startSeconds:(float)startSeconds + endSeconds:(float)endSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; + NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds]; + NSString *qualityValue = [WKYTPlayerView stringForPlaybackQuality:suggestedQuality]; + NSString *command = [NSString stringWithFormat:@"player.cueVideoById({'videoId': '%@', 'startSeconds': %@, 'endSeconds': %@, 'suggestedQuality': '%@'});", videoId, startSecondsValue, endSecondsValue, qualityValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +- (void)loadVideoById:(NSString *)videoId + startSeconds:(float)startSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; + NSString *qualityValue = [WKYTPlayerView stringForPlaybackQuality:suggestedQuality]; + NSString *command = [NSString stringWithFormat:@"player.loadVideoById('%@', %@, '%@');", + videoId, startSecondsValue, qualityValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +- (void)loadVideoById:(NSString *)videoId + startSeconds:(float)startSeconds + endSeconds:(float)endSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; + NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds]; + NSString *qualityValue = [WKYTPlayerView stringForPlaybackQuality:suggestedQuality]; + NSString *command = [NSString stringWithFormat:@"player.loadVideoById({'videoId': '%@', 'startSeconds': %@, 'endSeconds': %@, 'suggestedQuality': '%@'});",videoId, startSecondsValue, endSecondsValue, qualityValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +- (void)cueVideoByURL:(NSString *)videoURL + startSeconds:(float)startSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; + NSString *qualityValue = [WKYTPlayerView stringForPlaybackQuality:suggestedQuality]; + NSString *command = [NSString stringWithFormat:@"player.cueVideoByUrl('%@', %@, '%@');", + videoURL, startSecondsValue, qualityValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +- (void)cueVideoByURL:(NSString *)videoURL + startSeconds:(float)startSeconds + endSeconds:(float)endSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; + NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds]; + NSString *qualityValue = [WKYTPlayerView stringForPlaybackQuality:suggestedQuality]; + NSString *command = [NSString stringWithFormat:@"player.cueVideoByUrl('%@', %@, %@, '%@');", + videoURL, startSecondsValue, endSecondsValue, qualityValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +- (void)loadVideoByURL:(NSString *)videoURL + startSeconds:(float)startSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; + NSString *qualityValue = [WKYTPlayerView stringForPlaybackQuality:suggestedQuality]; + NSString *command = [NSString stringWithFormat:@"player.loadVideoByUrl('%@', %@, '%@');", + videoURL, startSecondsValue, qualityValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +- (void)loadVideoByURL:(NSString *)videoURL + startSeconds:(float)startSeconds + endSeconds:(float)endSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; + NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds]; + NSString *qualityValue = [WKYTPlayerView stringForPlaybackQuality:suggestedQuality]; + NSString *command = [NSString stringWithFormat:@"player.loadVideoByUrl('%@', %@, %@, '%@');", + videoURL, startSecondsValue, endSecondsValue, qualityValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +#pragma mark - Cueing methods for lists + +- (void)cuePlaylistByPlaylistId:(NSString *)playlistId + index:(int)index + startSeconds:(float)startSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + NSString *playlistIdString = [NSString stringWithFormat:@"'%@'", playlistId]; + [self cuePlaylist:playlistIdString + index:index + startSeconds:startSeconds + suggestedQuality:suggestedQuality]; +} + +- (void)cuePlaylistByVideos:(NSArray *)videoIds + index:(int)index + startSeconds:(float)startSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + [self cuePlaylist:[self stringFromVideoIdArray:videoIds] + index:index + startSeconds:startSeconds + suggestedQuality:suggestedQuality]; +} + +- (void)loadPlaylistByPlaylistId:(NSString *)playlistId + index:(int)index + startSeconds:(float)startSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + NSString *playlistIdString = [NSString stringWithFormat:@"'%@'", playlistId]; + [self loadPlaylist:playlistIdString + index:index + startSeconds:startSeconds + suggestedQuality:suggestedQuality]; +} + +- (void)loadPlaylistByVideos:(NSArray *)videoIds + index:(int)index + startSeconds:(float)startSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + [self loadPlaylist:[self stringFromVideoIdArray:videoIds] + index:index + startSeconds:startSeconds + suggestedQuality:suggestedQuality]; +} + +#pragma mark - Setting the playback rate + +- (void)getPlaybackRate:(void (^ __nullable)(float playbackRate, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.getPlaybackRate();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(0, error); + } else { + completionHandler([response floatValue], nil); + } + } + }]; +} + +- (void)setPlaybackRate:(float)suggestedRate { + NSString *command = [NSString stringWithFormat:@"player.setPlaybackRate(%f);", suggestedRate]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +- (void)getAvailablePlaybackRates:(void (^ __nullable)(NSArray * __nullable availablePlaybackRates, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.getAvailablePlaybackRates();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(nil, error); + } else { + NSData *playbackRateData = [response dataUsingEncoding:NSUTF8StringEncoding]; + NSError *jsonDeserializationError; + NSArray *playbackRates = [NSJSONSerialization JSONObjectWithData:playbackRateData + options:kNilOptions + error:&jsonDeserializationError]; + if (jsonDeserializationError) { + completionHandler(nil, jsonDeserializationError); + } + + completionHandler(playbackRates, nil); + } + } + }]; +} + +#pragma mark - Setting playback behavior for playlists + +- (void)setLoop:(BOOL)loop { + NSString *loopPlayListValue = [self stringForJSBoolean:loop]; + NSString *command = [NSString stringWithFormat:@"player.setLoop(%@);", loopPlayListValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +- (void)setShuffle:(BOOL)shuffle { + NSString *shufflePlayListValue = [self stringForJSBoolean:shuffle]; + NSString *command = [NSString stringWithFormat:@"player.setShuffle(%@);", shufflePlayListValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +#pragma mark - Playback status + +- (void)getVideoLoadedFraction:(void (^ __nullable)(float videoLoadedFraction, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.getVideoLoadedFraction();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(0, error); + } else { + completionHandler([response floatValue], nil); + } + } + }]; +} + +- (void)getPlayerState:(void (^ __nullable)(WKYTPlayerState playerState, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.getPlayerState();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(kWKYTPlayerStateUnknown, error); + } else { + if ([response isKindOfClass: [NSNumber class]]) { + NSNumber *value = (NSNumber *)response; + response = [value stringValue]; + } + completionHandler([WKYTPlayerView playerStateForString:response], nil); + } + } + }]; +} + +- (void)getCurrentTime:(void (^ __nullable)(float currentTime, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.getCurrentTime();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(0, error); + } else { + completionHandler([response floatValue], nil); + } + } + }]; +} + +// Playback quality +- (void)getPlaybackQuality:(void (^ __nullable)(WKYTPlaybackQuality playbackQuality, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.getPlaybackQuality();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(kWKYTPlaybackQualityUnknown, error); + } else { + completionHandler([WKYTPlayerView playbackQualityForString:response], nil); + } + } + }]; +} + +- (void)setPlaybackQuality:(WKYTPlaybackQuality)suggestedQuality { + NSString *qualityValue = [WKYTPlayerView stringForPlaybackQuality:suggestedQuality]; + NSString *command = [NSString stringWithFormat:@"player.setPlaybackQuality('%@');", qualityValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +#pragma mark - Video information methods + +- (void)getDuration:(void (^ __nullable)(NSTimeInterval duration, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.getDuration();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(0, error); + } else { + if (response != (id) [NSNull null]) { + completionHandler([response doubleValue], nil); + } + else { + completionHandler(0, nil); + } + } + } + }]; +} + +- (void)getVideoUrl:(void (^ __nullable)(NSURL * __nullable videoUrl, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.getVideoUrl();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(nil, error); + } else { + completionHandler([NSURL URLWithString:response], nil); + } + } + }]; +} + +- (void)getVideoEmbedCode:(void (^ __nullable)(NSString * __nullable videoEmbedCode, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.getVideoEmbedCode();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(nil, error); + } else { + completionHandler(response, nil); + } + } + }]; +} + +#pragma mark - Playlist methods + +- (void)getPlaylist:(void (^ __nullable)(NSArray * __nullable playlist, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.getPlaylist();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(nil, error); + } else { + + if ([response isKindOfClass:[NSNull class]]) { + completionHandler(nil, nil); + return; + } + + NSArray *videoIds; + + if ([response isKindOfClass:[NSArray class]]) + { + videoIds = (NSArray *)response; + } + else + { + NSData *playlistData = [response dataUsingEncoding:NSUTF8StringEncoding]; + NSError *jsonDeserializationError; + videoIds = [NSJSONSerialization JSONObjectWithData:playlistData + options:kNilOptions + error:&jsonDeserializationError]; + if (jsonDeserializationError) { + completionHandler(nil, jsonDeserializationError); + } + } + + completionHandler(videoIds, nil); + } + } + }]; +} + +- (void)getPlaylistIndex:(void (^ __nullable)(int playlistIndex, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.getPlaylistIndex();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(0, error); + } else { + completionHandler([response intValue], nil); + } + } + }]; +} + +#pragma mark - Playing a video in a playlist + +- (void)nextVideo { + [self stringFromEvaluatingJavaScript:@"player.nextVideo();" completionHandler:nil]; +} + +- (void)previousVideo { + [self stringFromEvaluatingJavaScript:@"player.previousVideo();" completionHandler:nil]; +} + +- (void)playVideoAt:(int)index { + NSString *command = + [NSString stringWithFormat:@"player.playVideoAt(%@);", [NSNumber numberWithInt:index]]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + + +#pragma mark - Changing the player volume + +/** + * Mutes the player. Corresponds to this method from + * the JavaScript API: + * https://developers.google.com/youtube/iframe_api_reference#mute + */ + +- (void)mute +{ + [self stringFromEvaluatingJavaScript:@"player.mute();" completionHandler:nil]; +} + +- (void)unMute +{ + [self stringFromEvaluatingJavaScript:@"player.unMute();" completionHandler:nil]; +} + +- (void)isMuted:(void (^ __nullable)(BOOL isMuted, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.isMuted();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(0, error); + } else { + completionHandler([response boolValue], nil); + } + } + }]; +} + + +#pragma mark - Helper methods + +- (void)getAvailableQualityLevels:(void (^ __nullable)(NSArray * __nullable availableQualityLevels, NSError * __nullable error))completionHandler +{ + [self stringFromEvaluatingJavaScript:@"player.getAvailableQualityLevels().toString();" completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + if (error) { + completionHandler(nil, error); + } else { + NSArray *rawQualityValues = [response componentsSeparatedByString:@","]; + NSMutableArray *levels = [[NSMutableArray alloc] init]; + for (NSString *rawQualityValue in rawQualityValues) { + WKYTPlaybackQuality quality = [WKYTPlayerView playbackQualityForString:rawQualityValue]; + [levels addObject:[NSNumber numberWithInt:quality]]; + } + + completionHandler(levels, nil); + } + } + }]; +} + +- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler +{ + NSURLRequest *request = navigationAction.request; + + if ([request.URL.host isEqual: self.originURL.host]) { + decisionHandler(WKNavigationActionPolicyAllow); + return; + } else if ([request.URL.scheme isEqual:@"ytplayer"]) { + [self notifyDelegateOfYouTubeCallbackUrl:request.URL]; + decisionHandler(WKNavigationActionPolicyCancel); + return; + } else if ([request.URL.scheme isEqual: @"http"] || [request.URL.scheme isEqual:@"https"]) { + if([self handleHttpNavigationToUrl:request.URL]) { + decisionHandler(WKNavigationActionPolicyAllow); + } else { + decisionHandler(WKNavigationActionPolicyCancel); + } + return; + } + + decisionHandler(WKNavigationActionPolicyAllow); +} + +- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error +{ + if (self.initialLoadingView) { + [self.initialLoadingView removeFromSuperview]; + } +} + +- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures { + if (!navigationAction.targetFrame.isMainFrame) { + // open link with target="_blank" in the same webView + [webView loadRequest:navigationAction.request]; + } + return nil; +} + +/** + * Convert a quality value from NSString to the typed enum value. + * + * @param qualityString A string representing playback quality. Ex: "small", "medium", "hd1080". + * @return An enum value representing the playback quality. + */ ++ (WKYTPlaybackQuality)playbackQualityForString:(NSString *)qualityString { + WKYTPlaybackQuality quality = kWKYTPlaybackQualityUnknown; + + if ([qualityString isEqualToString:kWKYTPlaybackQualitySmallQuality]) { + quality = kWKYTPlaybackQualitySmall; + } else if ([qualityString isEqualToString:kWKYTPlaybackQualityMediumQuality]) { + quality = kWKYTPlaybackQualityMedium; + } else if ([qualityString isEqualToString:kWKYTPlaybackQualityLargeQuality]) { + quality = kWKYTPlaybackQualityLarge; + } else if ([qualityString isEqualToString:kWKYTPlaybackQualityHD720Quality]) { + quality = kWKYTPlaybackQualityHD720; + } else if ([qualityString isEqualToString:kWKYTPlaybackQualityHD1080Quality]) { + quality = kWKYTPlaybackQualityHD1080; + } else if ([qualityString isEqualToString:kWKYTPlaybackQualityHighResQuality]) { + quality = kWKYTPlaybackQualityHighRes; + } else if ([qualityString isEqualToString:kWKYTPlaybackQualityAutoQuality]) { + quality = kWKYTPlaybackQualityAuto; + } + + return quality; +} + +/** + * Convert a |WKYTPlaybackQuality| value from the typed value to NSString. + * + * @param quality A |WKYTPlaybackQuality| parameter. + * @return An |NSString| value to be used in the JavaScript bridge. + */ ++ (NSString *)stringForPlaybackQuality:(WKYTPlaybackQuality)quality { + switch (quality) { + case kWKYTPlaybackQualitySmall: + return kWKYTPlaybackQualitySmallQuality; + case kWKYTPlaybackQualityMedium: + return kWKYTPlaybackQualityMediumQuality; + case kWKYTPlaybackQualityLarge: + return kWKYTPlaybackQualityLargeQuality; + case kWKYTPlaybackQualityHD720: + return kWKYTPlaybackQualityHD720Quality; + case kWKYTPlaybackQualityHD1080: + return kWKYTPlaybackQualityHD1080Quality; + case kWKYTPlaybackQualityHighRes: + return kWKYTPlaybackQualityHighResQuality; + case kWKYTPlaybackQualityAuto: + return kWKYTPlaybackQualityAutoQuality; + default: + return kWKYTPlaybackQualityUnknownQuality; + } +} + +/** + * Convert a state value from NSString to the typed enum value. + * + * @param stateString A string representing player state. Ex: "-1", "0", "1". + * @return An enum value representing the player state. + */ ++ (WKYTPlayerState)playerStateForString:(NSString *)stateString { + WKYTPlayerState state = kWKYTPlayerStateUnknown; + if ([stateString isEqualToString:kWKYTPlayerStateUnstartedCode]) { + state = kWKYTPlayerStateUnstarted; + } else if ([stateString isEqualToString:kWKYTPlayerStateEndedCode]) { + state = kWKYTPlayerStateEnded; + } else if ([stateString isEqualToString:kWKYTPlayerStatePlayingCode]) { + state = kWKYTPlayerStatePlaying; + } else if ([stateString isEqualToString:kWKYTPlayerStatePausedCode]) { + state = kWKYTPlayerStatePaused; + } else if ([stateString isEqualToString:kWKYTPlayerStateBufferingCode]) { + state = kWKYTPlayerStateBuffering; + } else if ([stateString isEqualToString:kWKYTPlayerStateCuedCode]) { + state = kWKYTPlayerStateQueued; + } + return state; +} + +/** + * Convert a state value from the typed value to NSString. + * + * @param state A |WKYTPlayerState| parameter. + * @return A string value to be used in the JavaScript bridge. + */ ++ (NSString *)stringForPlayerState:(WKYTPlayerState)state { + switch (state) { + case kWKYTPlayerStateUnstarted: + return kWKYTPlayerStateUnstartedCode; + case kWKYTPlayerStateEnded: + return kWKYTPlayerStateEndedCode; + case kWKYTPlayerStatePlaying: + return kWKYTPlayerStatePlayingCode; + case kWKYTPlayerStatePaused: + return kWKYTPlayerStatePausedCode; + case kWKYTPlayerStateBuffering: + return kWKYTPlayerStateBufferingCode; + case kWKYTPlayerStateQueued: + return kWKYTPlayerStateCuedCode; + default: + return kWKYTPlayerStateUnknownCode; + } +} + +#pragma mark - Private methods + +/** + * Private method to handle "navigation" to a callback URL of the format + * ytplayer://action?data=someData + * This is how the WKWebView communicates with the containing Objective-C code. + * Side effects of this method are that it calls methods on this class's delegate. + * + * @param url A URL of the format ytplayer://action?data=value. + */ +- (void)notifyDelegateOfYouTubeCallbackUrl: (NSURL *) url { + NSString *action = url.host; + + // We know the query can only be of the format ytplayer://action?data=SOMEVALUE, + // so we parse out the value. + NSString *query = url.query; + NSString *data; + if (query) { + data = [query componentsSeparatedByString:@"="][1]; + } + + if ([action isEqual:kWKYTPlayerCallbackOnReady]) { + if (self.initialLoadingView) { + [self.initialLoadingView removeFromSuperview]; + } + if ([self.delegate respondsToSelector:@selector(playerViewDidBecomeReady:)]) { + [self.delegate playerViewDidBecomeReady:self]; + } + } else if ([action isEqual:kWKYTPlayerCallbackOnStateChange]) { + if ([self.delegate respondsToSelector:@selector(playerView:didChangeToState:)]) { + WKYTPlayerState state = kWKYTPlayerStateUnknown; + + if ([data isEqual:kWKYTPlayerStateEndedCode]) { + state = kWKYTPlayerStateEnded; + } else if ([data isEqual:kWKYTPlayerStatePlayingCode]) { + state = kWKYTPlayerStatePlaying; + } else if ([data isEqual:kWKYTPlayerStatePausedCode]) { + state = kWKYTPlayerStatePaused; + } else if ([data isEqual:kWKYTPlayerStateBufferingCode]) { + state = kWKYTPlayerStateBuffering; + } else if ([data isEqual:kWKYTPlayerStateCuedCode]) { + state = kWKYTPlayerStateQueued; + } else if ([data isEqual:kWKYTPlayerStateUnstartedCode]) { + state = kWKYTPlayerStateUnstarted; + } + + [self.delegate playerView:self didChangeToState:state]; + } + } else if ([action isEqual:kWKYTPlayerCallbackOnPlaybackQualityChange]) { + if ([self.delegate respondsToSelector:@selector(playerView:didChangeToQuality:)]) { + WKYTPlaybackQuality quality = [WKYTPlayerView playbackQualityForString:data]; + [self.delegate playerView:self didChangeToQuality:quality]; + } + } else if ([action isEqual:kWKYTPlayerCallbackOnError]) { + if ([self.delegate respondsToSelector:@selector(playerView:receivedError:)]) { + WKYTPlayerError error = kWKYTPlayerErrorUnknown; + + if ([data isEqual:kWKYTPlayerErrorInvalidParamErrorCode]) { + error = kWKYTPlayerErrorInvalidParam; + } else if ([data isEqual:kWKYTPlayerErrorHTML5ErrorCode]) { + error = kWKYTPlayerErrorHTML5Error; + } else if ([data isEqual:kWKYTPlayerErrorNotEmbeddableErrorCode] || + [data isEqual:kWKYTPlayerErrorSameAsNotEmbeddableErrorCode]) { + error = kWKYTPlayerErrorNotEmbeddable; + } else if ([data isEqual:kWKYTPlayerErrorVideoNotFoundErrorCode] || + [data isEqual:kWKYTPlayerErrorCannotFindVideoErrorCode]) { + error = kWKYTPlayerErrorVideoNotFound; + } + + [self.delegate playerView:self receivedError:error]; + } + } else if ([action isEqualToString:kWKYTPlayerCallbackOnPlayTime]) { + if ([self.delegate respondsToSelector:@selector(playerView:didPlayTime:)]) { + float time = [data floatValue]; + [self.delegate playerView:self didPlayTime:time]; + } + } else if ([action isEqualToString:kWKYTPlayerCallbackOnYouTubeIframeAPIFailedToLoad]) { + if (self.initialLoadingView) { + [self.initialLoadingView removeFromSuperview]; + } + + if ([self.delegate respondsToSelector:@selector(playerViewIframeAPIDidFailedToLoad:)]) { + [self.delegate playerViewIframeAPIDidFailedToLoad:self]; + } + } +} + +- (BOOL)handleHttpNavigationToUrl:(NSURL *) url { + // Usually this means the user has clicked on the YouTube logo or an error message in the + // player. Most URLs should open in the browser. The only http(s) URL that should open in this + // WKWebView is the URL for the embed, which is of the format: + // http(s)://www.youtube.com/embed/[VIDEO ID]?[PARAMETERS] + NSError *error = NULL; + NSRegularExpression *ytRegex = + [NSRegularExpression regularExpressionWithPattern:kWKYTPlayerEmbedUrlRegexPattern + options:NSRegularExpressionCaseInsensitive + error:&error]; + NSTextCheckingResult *ytMatch = + [ytRegex firstMatchInString:url.absoluteString + options:0 + range:NSMakeRange(0, [url.absoluteString length])]; + + NSRegularExpression *adRegex = + [NSRegularExpression regularExpressionWithPattern:kWKYTPlayerAdUrlRegexPattern + options:NSRegularExpressionCaseInsensitive + error:&error]; + NSTextCheckingResult *adMatch = + [adRegex firstMatchInString:url.absoluteString + options:0 + range:NSMakeRange(0, [url.absoluteString length])]; + + NSRegularExpression *syndicationRegex = + [NSRegularExpression regularExpressionWithPattern:kWKYTPlayerSyndicationRegexPattern + options:NSRegularExpressionCaseInsensitive + error:&error]; + + NSTextCheckingResult *syndicationMatch = + [syndicationRegex firstMatchInString:url.absoluteString + options:0 + range:NSMakeRange(0, [url.absoluteString length])]; + + NSRegularExpression *oauthRegex = + [NSRegularExpression regularExpressionWithPattern:kWKYTPlayerOAuthRegexPattern + options:NSRegularExpressionCaseInsensitive + error:&error]; + NSTextCheckingResult *oauthMatch = + [oauthRegex firstMatchInString:url.absoluteString + options:0 + range:NSMakeRange(0, [url.absoluteString length])]; + + NSRegularExpression *staticProxyRegex = + [NSRegularExpression regularExpressionWithPattern:kWKYTPlayerStaticProxyRegexPattern + options:NSRegularExpressionCaseInsensitive + error:&error]; + NSTextCheckingResult *staticProxyMatch = + [staticProxyRegex firstMatchInString:url.absoluteString + options:0 + range:NSMakeRange(0, [url.absoluteString length])]; + + if (ytMatch || adMatch || oauthMatch || staticProxyMatch || syndicationMatch) { + return YES; + } else { + [[UIApplication sharedApplication] openURL:url]; + return NO; + } +} + + +/** + * Private helper method to load an iframe player with the given player parameters. + * + * @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:(NSDictionary *)additionalPlayerParams { + NSDictionary *playerCallbacks = @{ + @"onReady" : @"onReady", + @"onStateChange" : @"onStateChange", + @"onPlaybackQualityChange" : @"onPlaybackQualityChange", + @"onError" : @"onPlayerError" + }; + NSMutableDictionary *playerParams = [[NSMutableDictionary alloc] init]; + if (additionalPlayerParams) { + [playerParams addEntriesFromDictionary:additionalPlayerParams]; + } + if (![playerParams objectForKey:@"height"]) { + [playerParams setValue:@"100%" forKey:@"height"]; + } + if (![playerParams objectForKey:@"width"]) { + [playerParams setValue:@"100%" forKey:@"width"]; + } + + [playerParams setValue:playerCallbacks forKey:@"events"]; + + if ([playerParams objectForKey:@"playerVars"]) { + NSMutableDictionary *playerVars = [[NSMutableDictionary alloc] init]; + [playerVars addEntriesFromDictionary:[playerParams objectForKey:@"playerVars"]]; + + if (![playerVars objectForKey:@"origin"]) { + self.originURL = [NSURL URLWithString:@"about:blank"]; + } else { + self.originURL = [NSURL URLWithString: [playerVars objectForKey:@"origin"]]; + } + } else { + // This must not be empty so we can render a '{}' in the output JSON + [playerParams setValue:[[NSDictionary alloc] init] forKey:@"playerVars"]; + } + + // Remove the existing webView to reset any state + [self.webView removeFromSuperview]; + _webView = [self createNewWebView]; + [self addSubview:self.webView]; + + self.webView.translatesAutoresizingMaskIntoConstraints = NO; + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self.webView + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeTop + multiplier:1.0 + constant:0.0]; + NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:self.webView + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeLeft + multiplier:1.0 + constant:0.0]; + NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:self.webView + attribute:NSLayoutAttributeRight + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeRight + multiplier:1.0 + constant:0.0]; + NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:self.webView + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeBottom + multiplier:1.0 + constant:0.0]; + NSArray *constraints = @[topConstraint, leftConstraint, rightConstraint, bottomConstraint]; + [self addConstraints:constraints]; + + NSError *error = nil; + NSString *path = [additionalPlayerParams objectForKey:@"templatePath"]; + + //in case path to the HTML template wan't provided from the outside + if (!path) { + path = [[NSBundle bundleForClass:[WKYTPlayerView class]] pathForResource:@"YTPlayerView-iframe-player" + ofType:@"html" + inDirectory:@"Assets"]; + } + + // in case of using Swift and embedded frameworks, resources included not in main bundle, + // but in framework bundle + if (!path) { + path = [[[self class] frameworkBundle] pathForResource:@"YTPlayerView-iframe-player" + ofType:@"html" + inDirectory:@"Assets"]; + } + + NSString *embedHTMLTemplate = + [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; + + if (error) { + NSLog(@"Received error rendering template: %@", error); + return NO; + } + + // Render the playerVars as a JSON dictionary. + NSError *jsonRenderingError = nil; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:playerParams + options:NSJSONWritingPrettyPrinted + error:&jsonRenderingError]; + if (jsonRenderingError) { + NSLog(@"Attempted configuration of player with invalid playerVars: %@ \tError: %@", + playerParams, + jsonRenderingError); + return NO; + } + + NSString *playerVarsJsonString = + [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + + NSString *embedHTML = [NSString stringWithFormat:embedHTMLTemplate, playerVarsJsonString]; + [self.webView loadHTMLString:embedHTML baseURL: self.originURL]; + self.webView.navigationDelegate = self; + self.webView.UIDelegate = self; + + if ([self.delegate respondsToSelector:@selector(playerViewPreferredInitialLoadingView:)]) { + UIView *initialLoadingView = [self.delegate playerViewPreferredInitialLoadingView:self]; + if (initialLoadingView) { + initialLoadingView.frame = self.bounds; + initialLoadingView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self addSubview:initialLoadingView]; + self.initialLoadingView = initialLoadingView; + } + } + + return YES; +} + +/** + * Private method for cueing both cases of playlist ID and array of video IDs. Cueing + * a playlist does not start playback. + * + * @param cueingString A JavaScript string representing an array, playlist ID or list of + * video IDs to play with the playlist player. + * @param index 0-index position of video to start playback on. + * @param startSeconds Seconds after start of video to begin playback. + * @param suggestedQuality Suggested WKYTPlaybackQuality to play the videos. + */ +- (void)cuePlaylist:(NSString *)cueingString + index:(int)index + startSeconds:(float)startSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + NSNumber *indexValue = [NSNumber numberWithInt:index]; + NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; + NSString *qualityValue = [WKYTPlayerView stringForPlaybackQuality:suggestedQuality]; + NSString *command = [NSString stringWithFormat:@"player.cuePlaylist(%@, %@, %@, '%@');", + cueingString, indexValue, startSecondsValue, qualityValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +/** + * Private method for loading both cases of playlist ID and array of video IDs. Loading + * a playlist automatically starts playback. + * + * @param cueingString A JavaScript string representing an array, playlist ID or list of + * video IDs to play with the playlist player. + * @param index 0-index position of video to start playback on. + * @param startSeconds Seconds after start of video to begin playback. + * @param suggestedQuality Suggested WKYTPlaybackQuality to play the videos. + */ +- (void)loadPlaylist:(NSString *)cueingString + index:(int)index + startSeconds:(float)startSeconds + suggestedQuality:(WKYTPlaybackQuality)suggestedQuality { + NSNumber *indexValue = [NSNumber numberWithInt:index]; + NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; + NSString *qualityValue = [WKYTPlayerView stringForPlaybackQuality:suggestedQuality]; + NSString *command = [NSString stringWithFormat:@"player.loadPlaylist(%@, %@, %@, '%@');", + cueingString, indexValue, startSecondsValue, qualityValue]; + [self stringFromEvaluatingJavaScript:command completionHandler:nil]; +} + +/** + * Private helper method for converting an NSArray of video IDs into its JavaScript equivalent. + * + * @param videoIds An array of video ID strings to convert into JavaScript format. + * @return A JavaScript array in String format containing video IDs. + */ +- (NSString *)stringFromVideoIdArray:(NSArray *)videoIds { + NSMutableArray *formattedVideoIds = [[NSMutableArray alloc] init]; + + for (id unformattedId in videoIds) { + [formattedVideoIds addObject:[NSString stringWithFormat:@"'%@'", unformattedId]]; + } + + return [NSString stringWithFormat:@"[%@]", [formattedVideoIds componentsJoinedByString:@", "]]; +} + +/** + * Private method for evaluating JavaScript in the WebView. + * + * @param jsToExecute The JavaScript code in string format that we want to execute. + */ +- (void)stringFromEvaluatingJavaScript:(NSString *)jsToExecute completionHandler:(void (^ __nullable)(NSString * __nullable response, NSError * __nullable error))completionHandler{ + [self.webView evaluateJavaScript:jsToExecute completionHandler:^(NSString * _Nullable response, NSError * _Nullable error) { + if (completionHandler) { + completionHandler(response, error); + } + }]; +} + +/** + * Private method to convert a Objective-C BOOL value to JS boolean value. + * + * @param boolValue Objective-C BOOL value. + * @return JavaScript Boolean value, i.e. "true" or "false". + */ +- (NSString *)stringForJSBoolean:(BOOL)boolValue { + return boolValue ? @"true" : @"false"; +} + +#pragma mark - Exposed for Testing + +- (void)setWebView:(WKWebView *)webView { + _webView = webView; +} + +- (WKWebView *)createNewWebView { + + // WKWebView equivalent for UI Web View's scalesPageToFit + // + NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"; + + WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; + WKUserContentController *wkUController = [[WKUserContentController alloc] init]; + [wkUController addUserScript:wkUScript]; + + WKWebViewConfiguration *configuration = [WKWebViewConfiguration new]; + + configuration.userContentController = wkUController; + + configuration.allowsInlineMediaPlayback = YES; + configuration.mediaPlaybackRequiresUserAction = NO; + + WKWebView *webView = [[WKWebView alloc] initWithFrame:self.bounds configuration:configuration]; + webView.scrollView.scrollEnabled = NO; + webView.scrollView.bounces = NO; + + if ([self.delegate respondsToSelector:@selector(playerViewPreferredWebViewBackgroundColor:)]) { + webView.backgroundColor = [self.delegate playerViewPreferredWebViewBackgroundColor:self]; + if (webView.backgroundColor == [UIColor clearColor]) { + webView.opaque = NO; + } + } + + return webView; +} + +- (void)removeWebView { + [self.webView removeFromSuperview]; + self.webView = nil; +} + ++ (NSBundle *)frameworkBundle { + static NSBundle* frameworkBundle = nil; + static dispatch_once_t predicate; + dispatch_once(&predicate, ^{ + NSString* mainBundlePath = [[NSBundle bundleForClass:[WKYTPlayerView class]] resourcePath]; + NSString* frameworkBundlePath = [mainBundlePath stringByAppendingPathComponent:@"WKYTPlayerView.bundle"]; + frameworkBundle = [NSBundle bundleWithPath:frameworkBundlePath]; + }); + return frameworkBundle; +} + +@end diff --git a/metro.config.js b/metro.config.js index 47579372c..585fb667c 100644 --- a/metro.config.js +++ b/metro.config.js @@ -40,6 +40,7 @@ const config = { }; }, }, + maxWorkers: 4, }; module.exports = config;