diff --git a/app/components/post_list/post/body/content/youtube/index.ts b/app/components/post_list/post/body/content/youtube/index.ts deleted file mode 100644 index 22ad39fbc7..0000000000 --- a/app/components/post_list/post/body/content/youtube/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; -import withObservables from '@nozbe/with-observables'; - -import {observeConfigValue} from '@queries/servers/system'; - -import YouTube from './youtube'; - -import type {WithDatabaseArgs} from '@typings/database/database'; - -const enhance = withObservables([], ({database}: WithDatabaseArgs) => ({ - googleDeveloperKey: observeConfigValue(database, 'GoogleDeveloperKey'), -})); - -export default withDatabase(enhance(YouTube)); diff --git a/app/components/post_list/post/body/content/youtube/index.tsx b/app/components/post_list/post/body/content/youtube/index.tsx new file mode 100644 index 0000000000..cb208ed1a8 --- /dev/null +++ b/app/components/post_list/post/body/content/youtube/index.tsx @@ -0,0 +1,102 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useCallback} from 'react'; +import {useIntl} from 'react-intl'; +import {Alert, Image, StyleSheet, TouchableOpacity, View} from 'react-native'; + +import ProgressiveImage from '@components/progressive_image'; +import {useIsTablet} from '@hooks/device'; +import {emptyFunction} from '@utils/general'; +import {calculateDimensions, getViewPortWidth} from '@utils/images'; +import {getYouTubeVideoId, tryOpenURL} from '@utils/url'; + +type YouTubeProps = { + isReplyPost: boolean; + layoutWidth?: number; + metadata: PostMetadata; +} + +const MAX_YOUTUBE_IMAGE_HEIGHT = 202; +const MAX_YOUTUBE_IMAGE_WIDTH = 360; + +const styles = StyleSheet.create({ + imageContainer: { + alignItems: 'flex-start', + justifyContent: 'flex-start', + marginBottom: 6, + marginTop: 10, + }, + image: { + alignItems: 'center', + borderRadius: 3, + justifyContent: 'center', + marginVertical: 1, + }, + playButton: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, +}); + +const YouTube = ({isReplyPost, layoutWidth, metadata}: YouTubeProps) => { + const intl = useIntl(); + const isTablet = useIsTablet(); + const link = metadata.embeds![0].url; + const videoId = getYouTubeVideoId(link); + const dimensions = calculateDimensions( + MAX_YOUTUBE_IMAGE_HEIGHT, + MAX_YOUTUBE_IMAGE_WIDTH, + layoutWidth || getViewPortWidth(isReplyPost, isTablet), + ); + + const playYouTubeVideo = useCallback(() => { + const onError = () => { + Alert.alert( + intl.formatMessage({ + id: 'mobile.link.error.title', + defaultMessage: 'Error', + }), + intl.formatMessage({ + id: 'mobile.link.error.text', + defaultMessage: 'Unable to open the link.', + }), + ); + }; + + tryOpenURL(link, onError); + }, [link, intl.locale]); + + let imgUrl; + if (metadata.images) { + imgUrl = Object.keys(metadata.images)[0]; + } + + if (!imgUrl) { + // Fallback to default YouTube thumbnail if available + imgUrl = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`; + } + + return ( + + + + + + + + ); +}; + +export default React.memo(YouTube); diff --git a/app/components/post_list/post/body/content/youtube/youtube.tsx b/app/components/post_list/post/body/content/youtube/youtube.tsx deleted file mode 100644 index 7c9b2095af..0000000000 --- a/app/components/post_list/post/body/content/youtube/youtube.tsx +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {useCallback} from 'react'; -import {useIntl} from 'react-intl'; -import {Alert, Image, Platform, StatusBar, StyleSheet, TouchableOpacity, View} from 'react-native'; -import {YouTubeStandaloneAndroid, YouTubeStandaloneIOS} from 'react-native-youtube'; - -import ProgressiveImage from '@components/progressive_image'; -import {useIsTablet} from '@hooks/device'; -import {emptyFunction} from '@utils/general'; -import {calculateDimensions, getViewPortWidth} from '@utils/images'; -import {getYouTubeVideoId, tryOpenURL} from '@utils/url'; - -type YouTubeProps = { - googleDeveloperKey?: string; - isReplyPost: boolean; - layoutWidth?: number; - metadata: PostMetadata; -} - -const MAX_YOUTUBE_IMAGE_HEIGHT = 202; -const MAX_YOUTUBE_IMAGE_WIDTH = 360; -const timeRegex = /[\\?&](t|start|time_continue)=([0-9]+h)?([0-9]+m)?([0-9]+s?)/; - -const styles = StyleSheet.create({ - imageContainer: { - alignItems: 'flex-start', - justifyContent: 'flex-start', - marginBottom: 6, - marginTop: 10, - }, - image: { - alignItems: 'center', - borderRadius: 3, - justifyContent: 'center', - marginVertical: 1, - }, - playButton: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, -}); - -const YouTube = ({googleDeveloperKey, isReplyPost, layoutWidth, metadata}: YouTubeProps) => { - const intl = useIntl(); - const isTablet = useIsTablet(); - const link = metadata.embeds![0].url; - const videoId = getYouTubeVideoId(link); - const dimensions = calculateDimensions( - MAX_YOUTUBE_IMAGE_HEIGHT, - MAX_YOUTUBE_IMAGE_WIDTH, - layoutWidth || getViewPortWidth(isReplyPost, isTablet), - ); - - const getYouTubeTime = () => { - const time = link.match(timeRegex); - if (!time || !time[0]) { - return 0; - } - - const hours = time[2] ? time[2].match(/([0-9]+)h/) : null; - const minutes = time[3] ? time[3].match(/([0-9]+)m/) : null; - const seconds = time[4] ? time[4].match(/([0-9]+)s?/) : null; - - let ticks = 0; - - if (hours && hours[1]) { - ticks += parseInt(hours[1], 10) * 3600; - } - - if (minutes && minutes[1]) { - ticks += parseInt(minutes[1], 10) * 60; - } - - if (seconds && seconds[1]) { - ticks += parseInt(seconds[1], 10); - } - - return ticks; - }; - - const playYouTubeVideo = useCallback(() => { - const startTime = getYouTubeTime(); - - if (googleDeveloperKey) { - if (Platform.OS === 'ios') { - YouTubeStandaloneIOS. - playVideo(videoId, startTime). - then(playYouTubeVideoEnded). - catch(playYouTubeVideoError); - return; - } - - YouTubeStandaloneAndroid.playVideo({ - apiKey: googleDeveloperKey, - videoId, - autoplay: true, - startTime, - }).catch(playYouTubeVideoError); - } else { - const onError = () => { - Alert.alert( - intl.formatMessage({ - id: 'mobile.link.error.title', - defaultMessage: 'Error', - }), - intl.formatMessage({ - id: 'mobile.link.error.text', - defaultMessage: 'Unable to open the link.', - }), - ); - }; - - tryOpenURL(link, onError); - } - }, []); - - const playYouTubeVideoEnded = () => { - if (Platform.OS === 'ios') { - StatusBar.setHidden(false); - } - }; - - const playYouTubeVideoError = (errorMessage: string) => { - Alert.alert( - intl.formatMessage({ - id: 'mobile.youtube_playback_error.title', - defaultMessage: 'YouTube playback error', - }), - intl.formatMessage({ - id: 'mobile.youtube_playback_error.description', - defaultMessage: 'An error occurred while trying to play the YouTube video.\nDetails: {details}', - }, { - details: errorMessage, - }), - ); - }; - - let imgUrl; - if (metadata.images) { - imgUrl = Object.keys(metadata.images)[0]; - } - - if (!imgUrl) { - // Fallback to default YouTube thumbnail if available - imgUrl = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`; - } - - return ( - - - - - - - - ); -}; - -export default React.memo(YouTube); diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 8ad2779e91..3df5c9ebab 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -620,7 +620,6 @@ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf", "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", - "${PODS_ROOT}/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/iosMath/mathFonts.bundle", ); name = "[CP] Copy Pods Resources"; @@ -642,7 +641,6 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/WKYTPlayerView.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/mathFonts.bundle", ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/Podfile b/ios/Podfile index 99b8c4b101..b4d42696b8 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -27,7 +27,6 @@ target 'Mattermost' do pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi', :modular_headers => true pod 'simdjson', path: '../node_modules/@nozbe/simdjson' - pod 'XCDYouTubeKit', '2.8.2' pod 'Swime', '3.0.6' # TODO: Remove this once upstream PR https://github.com/daltoniam/Starscream/pull/871 is merged @@ -46,9 +45,6 @@ end react_native_post_install(installer) __apply_Xcode_12_5_M1_post_install_workaround(installer) - puts 'Patching XCDYouTube so it can playback videos' - %x(patch Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoOperation.m < patches/XCDYouTubeVideoOperation.patch) - puts 'Patching Alamofire to include X-Uncompressed-Content-Length to measure download progress' %x(patch Pods/Alamofire/Source/SessionDelegate.swift < patches/SessionDelegate.patch) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7394ade679..8a53a3f218 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -123,9 +123,6 @@ PODS: - RCT-Folly (= 2021.06.28.00-v2) - RCTRequired (= 0.68.0) - React-Core (= 0.68.0) - - RCTYouTube (2.0.2): - - React - - YoutubePlayer-in-WKWebView (~> 0.3.1) - React (0.68.0): - React-Core (= 0.68.0) - React-Core/DevSupport (= 0.68.0) @@ -536,11 +533,9 @@ PODS: - WatermelonDB (0.24.0): - React - React-jsi - - XCDYouTubeKit (2.8.2) - Yoga (1.14.0) - YogaKit (1.18.1): - Yoga (~> 1.14) - - YoutubePlayer-in-WKWebView (0.3.8) DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) @@ -581,7 +576,6 @@ DEPENDENCIES: - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - - RCTYouTube (from `../node_modules/react-native-youtube`) - React (from `../node_modules/react-native/`) - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) - React-Codegen (from `build/generated/ios`) @@ -649,7 +643,6 @@ DEPENDENCIES: - Starscream (from `https://github.com/mattermost/Starscream.git`, commit `cb83dd247339ff6c155f0e749d6fe2cc145f5283`) - Swime (= 3.0.6) - "WatermelonDB (from `../node_modules/@nozbe/watermelondb`)" - - XCDYouTubeKit (= 2.8.2) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: @@ -679,9 +672,7 @@ SPEC REPOS: - SocketRocket - SwiftyJSON - Swime - - XCDYouTubeKit - YogaKit - - YoutubePlayer-in-WKWebView EXTERNAL SOURCES: boost: @@ -712,8 +703,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/Libraries/RCTRequired" RCTTypeSafety: :path: "../node_modules/react-native/Libraries/TypeSafety" - RCTYouTube: - :path: "../node_modules/react-native-youtube" React: :path: "../node_modules/react-native/" React-callinvoker: @@ -884,7 +873,6 @@ SPEC CHECKSUMS: RCT-Folly: 4d8508a426467c48885f1151029bc15fa5d7b3b8 RCTRequired: bab4a7c3d7eb9553b13773ee190f279712efd1fc RCTTypeSafety: efbeb6e450ff6cef8e19c2cb5314c6d8bfeeef77 - RCTYouTube: a8bb45705622a6fc9decf64be04128d3658ed411 React: 28e4d45839b7d0fd9512af899e0379a17a5172ec React-callinvoker: 5585d1ef6795786f288690b19e08bed253c33155 React-Codegen: 80ce98fda08a8ddb6f47116375ae2c1670bf8cda @@ -956,11 +944,9 @@ SPEC CHECKSUMS: SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b WatermelonDB: baec390a1039dcebeee959218900c978af3407c9 - XCDYouTubeKit: 79baadb0560673a67c771eba45f83e353fd12c1f Yoga: 6671cf077f614314c22fd09ddf87d7abeee64e96 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a - YoutubePlayer-in-WKWebView: 4fca3b4f6f09940077bfbae7bddb771f2b43aacd -PODFILE CHECKSUM: 9b7206e144619146e3d5456de646e7d0c34b25ec +PODFILE CHECKSUM: bb161ae7bf12d0d04e6c9584b673aeeee5bf3639 COCOAPODS: 1.11.3 diff --git a/ios/patches/XCDYouTubeVideoOperation.patch b/ios/patches/XCDYouTubeVideoOperation.patch deleted file mode 100644 index 041d8cb55f..0000000000 --- a/ios/patches/XCDYouTubeVideoOperation.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- Pods/XCDYouTubeKit/XCDYouTubeKit/XCDYouTubeVideoOperation.m 2021-05-28 20:53:40.000000000 -0400 -+++ XCDYouTubeVideoOperation.m 2021-05-28 20:55:18.000000000 -0400 -@@ -119,7 +119,7 @@ - NSString *eventLabel = [self.eventLabels objectAtIndex:0]; - [self.eventLabels removeObjectAtIndex:0]; - -- NSDictionary *query = @{ @"video_id": self.videoIdentifier, @"hl": self.languageIdentifier, @"el": eventLabel, @"ps": @"default" }; -+ NSDictionary *query = @{ @"video_id": self.videoIdentifier, @"hl": self.languageIdentifier, @"el": eventLabel, @"ps": @"default", @"html5": @"1"}; - NSString *queryString = XCDQueryStringWithDictionary(query); - NSURL *videoInfoURL = [NSURL URLWithString:[@"https://www.youtube.com/get_video_info?" stringByAppendingString:queryString]]; - [self startRequestWithURL:videoInfoURL type:XCDYouTubeRequestTypeGetVideoInfo]; diff --git a/package-lock.json b/package-lock.json index 1edd7d4b63..ed3541965c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -85,7 +85,6 @@ "react-native-vector-icons": "9.1.0", "react-native-video": "5.2.0", "react-native-webview": "11.18.1", - "react-native-youtube": "2.0.2", "react-syntax-highlighter": "15.5.0", "reanimated-bottom-sheet": "1.0.0-alpha.22", "rn-placeholder": "3.0.3", @@ -19584,17 +19583,6 @@ "node": ">=8" } }, - "node_modules/react-native-youtube": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/react-native-youtube/-/react-native-youtube-2.0.2.tgz", - "integrity": "sha512-tyoGG8mqv0q2327BUYCXBmloUO8twFTOsGdwYvj0tiINAwtxNid8bS0KxnjViH3f/HmBqATDbPwhHpNXpOCsLA==", - "dependencies": { - "prop-types": "^15.5.0" - }, - "peerDependencies": { - "react-native": ">=0.60" - } - }, "node_modules/react-native/node_modules/@jest/types": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", @@ -38611,14 +38599,6 @@ } } }, - "react-native-youtube": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/react-native-youtube/-/react-native-youtube-2.0.2.tgz", - "integrity": "sha512-tyoGG8mqv0q2327BUYCXBmloUO8twFTOsGdwYvj0tiINAwtxNid8bS0KxnjViH3f/HmBqATDbPwhHpNXpOCsLA==", - "requires": { - "prop-types": "^15.5.0" - } - }, "react-refresh": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", diff --git a/package.json b/package.json index af2289d6bb..c5a1128687 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,6 @@ "react-native-vector-icons": "9.1.0", "react-native-video": "5.2.0", "react-native-webview": "11.18.1", - "react-native-youtube": "2.0.2", "react-syntax-highlighter": "15.5.0", "reanimated-bottom-sheet": "1.0.0-alpha.22", "rn-placeholder": "3.0.3", diff --git a/patches/react-native-youtube+2.0.2.patch b/patches/react-native-youtube+2.0.2.patch deleted file mode 100644 index 8c0d8752ac..0000000000 --- a/patches/react-native-youtube+2.0.2.patch +++ /dev/null @@ -1,102 +0,0 @@ -diff --git a/node_modules/react-native-youtube/RCTYouTubeStandalone.m b/node_modules/react-native-youtube/RCTYouTubeStandalone.m -index fabd291..45b322b 100644 ---- a/node_modules/react-native-youtube/RCTYouTubeStandalone.m -+++ b/node_modules/react-native-youtube/RCTYouTubeStandalone.m -@@ -5,6 +5,15 @@ - #endif - @import AVKit; - -+@interface AVPlayerViewControllerRotation : AVPlayerViewController -+@end -+ -+@implementation AVPlayerViewControllerRotation -+- (UIInterfaceOrientationMask)supportedInterfaceOrientations { -+ return UIInterfaceOrientationMaskAllButUpsideDown; -+} -+@end -+ - @implementation RCTYouTubeStandalone { - RCTPromiseResolveBlock resolver; - RCTPromiseRejectBlock rejecter; -@@ -14,6 +23,7 @@ RCT_EXPORT_MODULE(); - - RCT_REMAP_METHOD(playVideo, - playVideoWithResolver:(NSString*)videoId -+ startTime:(NSNumber* _Nonnull)startTime - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) - { -@@ -22,10 +32,10 @@ RCT_REMAP_METHOD(playVideo, - #else - dispatch_async(dispatch_get_main_queue(), ^{ - UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; -- AVPlayerViewController *playerViewController = [AVPlayerViewController new]; -+ AVPlayerViewControllerRotation *playerViewController = [AVPlayerViewControllerRotation new]; - [root presentViewController:playerViewController animated:YES completion:nil]; - -- __weak AVPlayerViewController *weakPlayerViewController = playerViewController; -+ __weak AVPlayerViewControllerRotation *weakPlayerViewController = playerViewController; - - [[XCDYouTubeClient defaultClient] getVideoWithIdentifier:videoId - completionHandler:^(XCDYouTubeVideo * _Nullable video, NSError * _Nullable error) { -@@ -38,10 +48,18 @@ RCT_REMAP_METHOD(playVideo, - streamURLs[@(XCDYouTubeVideoQualitySmall240) - ]; - -- weakPlayerViewController.player = [AVPlayer playerWithURL:streamURL]; -- [weakPlayerViewController.player play]; -- -- resolve(@"YouTubeStandaloneIOS player launched successfully"); -+ @try { -+ CMTime initialPlaybackTime = CMTimeMakeWithSeconds([startTime doubleValue], 1); -+ weakPlayerViewController.player = [AVPlayer playerWithURL:streamURL]; -+ [weakPlayerViewController.player seekToTime:initialPlaybackTime completionHandler: ^(BOOL finished) { -+ [weakPlayerViewController.player play]; -+ resolve(@"YouTubeStandaloneIOS player launched successfully"); -+ }]; -+ } -+ @catch (NSException *ex) { -+ reject(@"error", ex.reason, nil); -+ [root dismissViewControllerAnimated:YES completion:nil]; -+ } - } else { - reject(@"error", error.localizedDescription, nil); - [root dismissViewControllerAnimated:YES completion:nil]; -diff --git a/node_modules/react-native-youtube/YouTubeStandalone.ios.js b/node_modules/react-native-youtube/YouTubeStandalone.ios.js -index 0ee59c6..4a8294b 100644 ---- a/node_modules/react-native-youtube/YouTubeStandalone.ios.js -+++ b/node_modules/react-native-youtube/YouTubeStandalone.ios.js -@@ -4,4 +4,4 @@ const { YouTubeStandalone } = NativeModules; - - export const YouTubeStandaloneIOS = !YouTubeStandalone - ? null -- : { playVideo: (videoId) => YouTubeStandalone.playVideo(videoId) }; -+ : { playVideo: (videoId, startTime) => YouTubeStandalone.playVideo(videoId, startTime) }; -diff --git a/node_modules/react-native-youtube/android/build.gradle b/node_modules/react-native-youtube/android/build.gradle -index 0417bc6..6249f65 100755 ---- a/node_modules/react-native-youtube/android/build.gradle -+++ b/node_modules/react-native-youtube/android/build.gradle -@@ -6,8 +6,9 @@ def safeExtGet(prop, fallback) { - - buildscript { - repositories { -+ mavenCentral() -+ mavenLocal() - google() -- jcenter() - } - - dependencies { -diff --git a/node_modules/react-native-youtube/main.d.ts b/node_modules/react-native-youtube/main.d.ts -index 80c7df5..849104b 100644 ---- a/node_modules/react-native-youtube/main.d.ts -+++ b/node_modules/react-native-youtube/main.d.ts -@@ -36,7 +36,7 @@ declare class YouTube extends React.Component { - } - - export declare const YouTubeStandaloneIOS: { -- playVideo(videoId: string): Promise; -+ playVideo(videoId: string, startTime: number): Promise; - }; - - export declare const YouTubeStandaloneAndroid: {