Remove react-native-youtube and allow the link to be handled natively (#6239)

This commit is contained in:
Elias Nahum
2022-05-06 14:28:33 -04:00
committed by GitHub
parent 9d3e1fbf59
commit 10f7f008e4
10 changed files with 103 additions and 344 deletions

View File

@@ -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));

View File

@@ -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 (
<TouchableOpacity
style={[styles.imageContainer, {height: dimensions.height}]}
onPress={playYouTubeVideo}
>
<ProgressiveImage
id={imgUrl}
isBackgroundImage={true}
imageUri={imgUrl}
style={[styles.image, dimensions]}
resizeMode='cover'
onError={emptyFunction}
>
<View style={styles.playButton}>
<Image source={require('@assets/images/icons/youtube-play-icon.png')}/>
</View>
</ProgressiveImage>
</TouchableOpacity>
);
};
export default React.memo(YouTube);

View File

@@ -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 (
<TouchableOpacity
style={[styles.imageContainer, {height: dimensions.height}]}
onPress={playYouTubeVideo}
>
<ProgressiveImage
id={imgUrl}
isBackgroundImage={true}
imageUri={imgUrl}
style={[styles.image, dimensions]}
resizeMode='cover'
onError={emptyFunction}
>
<View style={styles.playButton}>
<Image source={require('@assets/images/icons/youtube-play-icon.png')}/>
</View>
</ProgressiveImage>
</TouchableOpacity>
);
};
export default React.memo(YouTube);

View File

@@ -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;

View File

@@ -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)

View File

@@ -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

View File

@@ -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];

20
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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<YouTubeProps> {
}
export declare const YouTubeStandaloneIOS: {
- playVideo(videoId: string): Promise<void>;
+ playVideo(videoId: string, startTime: number): Promise<void>;
};
export declare const YouTubeStandaloneAndroid: {