Commit 22311a3e authored by Martin Santangelo's avatar Martin Santangelo

(fix) ios video preview and upload with ph:// paths

1 merge request!377WIP: Udpate app to react native 0.61.1 and jitsi 2.4.0
import React, { PureComponent } from 'react';
import { StyleSheet, View, Image } from 'react-native';
import { StyleSheet, View, Image, Platform } from 'react-native';
import MindsVideo from '../media/MindsVideo';
......@@ -15,25 +15,29 @@ export default class CapturePreview extends PureComponent {
switch (this.props.type) {
case 'image/gif':
case 'image/jpeg':
case 'image':
default:
body = <Image
resizeMode='contain'
source={{ uri: this.props.uri }}
style={styles.preview}
/>
body = (
<Image
resizeMode="contain"
source={{uri: this.props.uri}}
style={styles.preview}
/>
);
break;
case 'video/mp4':
body = <View style={styles.preview}>
<MindsVideo video={{ 'uri': this.props.uri }} />
</View>
case 'video/quicktime':
case 'video/x-m4v':
case 'video':
body = (
<View style={styles.preview}>
<MindsVideo video={{uri: this.props.uri}} />
</View>
);
break;
}
return (
<View style={styles.wrapper}>
{body}
</View>
);
return <View style={styles.wrapper}>{body}</View>;
}
}
......@@ -43,7 +47,7 @@ const styles = StyleSheet.create({
flex: 1,
flexDirection: 'row',
alignItems: 'stretch',
backgroundColor: 'black'
backgroundColor: 'black',
},
preview: {
flex: 1,
......
import { observable, action, extendObservable } from 'mobx'
import { Platform, Alert } from 'react-native';
import rnFS from 'react-native-fs';
import MediaMeta from 'react-native-media-meta';
import fileType from 'react-native-file-type';
import {observable, action} from 'mobx';
import {Alert, Platform} from 'react-native';
import RNConvertPhAsset from 'react-native-convert-ph-asset';
import attachmentService from '../services/attachment.service';
import {MINDS_MAX_VIDEO_LENGTH} from '../../config/Config';
import mindsService from '../services/minds.service';
import logService from '../services/log.service';
import i18n from '../services/i18n.service';
import mindsService from '../services/minds.service';
/**
* Attachment Store
......@@ -16,15 +13,14 @@ import i18n from '../services/i18n.service';
export default class AttachmentStore {
@observable hasAttachment = false;
@observable uploading = false;
@observable checkingVideoLength = false;
@observable progress = 0;
guid = '';
@observable uri = '';
@observable uri = '';
@observable type = '';
@observable license = '';
tempIosVideo = '';
guid = '';
fileName = null;
transcoding = false;
/**
* Attach media
......@@ -33,14 +29,10 @@ export default class AttachmentStore {
*/
@action
async attachMedia(media, extra = null) {
// no new media acepted if we are checking for video length
if (this.checkingVideoLength) return;
// validate media
const valid = await this.validate(media);
if (!valid) return;
if (this.transcoding) {
return;
}
console.log('ATTACHING', media, extra);
if (this.uploading) {
// abort current upload
this.cancelCurrentUpload();
......@@ -50,16 +42,45 @@ export default class AttachmentStore {
await attachmentService.deleteMedia(this.guid);
} catch (error) {
// we ignore delete error for now
logService.info('Error deleting the uploaded media '+this.guid);
logService.info('Error deleting the uploaded media ' + this.guid);
}
}
this.uri = media.uri;
this.type = media.type;
if (!await this.validate(media)) {
return;
}
this.setHasAttachment(true);
// correctly handle videos from ph:// paths on ios
if (
Platform.OS === 'ios' &&
media.type === 'video' &&
media.uri.startsWith('ph://')
) {
try {
this.transcoding = true;
const converted = await RNConvertPhAsset.convertVideoFromUrl({
url: media.uri,
convertTo: 'm4v',
quality: 'high',
});
media.type = converted.mimeType;
media.uri = converted.path;
media.filename = converted.filename;
} catch (error) {
Alert.alert('Error reading the video', 'Please try again');
} finally {
this.transcoding = false;
}
}
this.uri = media.uri;
this.type = media.type;
this.fileName = media.fileName;
try {
const uploadPromise = attachmentService.attachMedia(media, extra, (pct) => {
const uploadPromise = attachmentService.attachMedia(media, extra, pct => {
this.setProgress(pct);
});
......@@ -70,7 +91,9 @@ export default class AttachmentStore {
const result = await uploadPromise;
// ignore canceled
if ((uploadPromise.isCanceled && uploadPromise.isCanceled()) || !result) return;
if ((uploadPromise.isCanceled && uploadPromise.isCanceled()) || !result) {
return;
}
this.guid = result.guid;
} catch (err) {
this.clear();
......@@ -79,82 +102,31 @@ export default class AttachmentStore {
this.setUploading(false);
}
// delete temp ios video if necessary
if (this.tempIosVideo) {
rnFS.unlink(this.tempIosVideo);
this.tempIosVideo = '';
}
return this.guid;
}
/**
* Cancel current upload promise and request
*/
cancelCurrentUpload(clear=true)
{
this.uploadPromise && this.uploadPromise.cancel(() => {
if (clear) this.clear();
});
}
/**
* Validate media
* @param {object} media
*/
@action
async validate(media) {
if(!media.type){
const type = await fileType(media.path);
media.type = type.mime;
}
if (media.fileName && media.fileName.includes(' ')) media.fileName = media.fileName.replace(/\s/g, "_");
const settings = await mindsService.getSettings();
let videoPath = null;
switch (media.type) {
case 'video/mp4':
videoPath = media.path || media.uri.replace(/^.*:\/\//, '');
break;
case 'ALAssetTypeVideo':
// if video is selected from cameraroll we need to copy
await this.copyVideoIos(media);
videoPath = this.tempIosVideo;
media.type = 'video/mp4';
media.path = videoPath;
media.uri = 'file:\/\/'+videoPath;
break;
if (media.duration && media.duration > settings.max_video_length * 1000) {
Alert.alert(
i18n.t('sorry'),
i18n.t('attachment.tooLong', {minutes: settings.max_video_length / 60}),
);
return false;
}
if (videoPath) {
this.checkingVideoLength = true;
const meta = await MediaMeta.get(videoPath);
this.checkingVideoLength = false;
// check video length
if (meta.duration && meta.duration > (settings.max_video_length * 1000) ) {
Alert.alert(
i18n.t('sorry'),
i18n.t('attachment.tooLong', {minutes: (settings.max_video_length / 60)})
);
return false;
}
}
return true;
}
/**
* copy a video from ios library assets to temporal app folder
* @param {object} media
* Cancel current upload promise and request
*/
copyVideoIos(media) {
this.tempIosVideo = rnFS.TemporaryDirectoryPath+'MINDS-'+Date.now()+'.MP4'
return rnFS.copyAssetsVideoIOS(media.uri, this.tempIosVideo);
cancelCurrentUpload(clear = true) {
this.uploadPromise &&
this.uploadPromise.cancel(() => {
if (clear) {
this.clear();
}
});
}
/**
......@@ -165,7 +137,7 @@ export default class AttachmentStore {
try {
attachmentService.deleteMedia(this.guid);
this.clear();
return true
return true;
} catch (err) {
return false;
}
......@@ -177,12 +149,12 @@ export default class AttachmentStore {
@action
setProgress(value) {
this.progress = value
this.progress = value;
}
@action
setUploading(value) {
this.uploading = value
this.uploading = value;
}
@action
......@@ -205,11 +177,5 @@ export default class AttachmentStore {
this.checkingVideoLength = false;
this.uploading = false;
this.progress = 0;
if (this.tempIosVideo) {
rnFS.unlink(this.tempIosVideo);
this.tempIosVideo = '';
}
}
}
\ No newline at end of file
}
......@@ -40,28 +40,39 @@ class MindsVideo extends Component {
paused: true,
volume: 1,
loaded: true,
active: false,
active: !props.entity,
showOverlay: true,
fullScreen: false,
error: false,
inProgress: false,
video: {},
};
}
/**
* Derive state from props
* @param {object} nextProps
* @param {object} prevState
*/
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.video && nextProps.video.uri !== prevState.video.uri) {
return {
video: {uri: nextProps.video.uri},
};
}
return null;
}
/**
* On component will mount
*/
componentWillMount () {
componentDidMount() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true
})
if (!this.props.entity) {
this.setState({active: true})
}
});
this.onScreenBlur = this.props.navigation.addListener(
'didBlur',
......@@ -103,7 +114,7 @@ class MindsVideo extends Component {
};
onError = (err) => {
logService.exception('[MindsVideo]', err)
logService.exception('[MindsVideo]', new Error(err));
this.setState({ error: true, inProgress: false, });
};
......@@ -244,7 +255,7 @@ class MindsVideo extends Component {
onProgress = {this.onProgress}
onError={this.onError}
ignoreSilentSwitch={'obey'}
source={{ uri: video.uri.replace('file://',''), type: 'mp4' }}
source={this.state.video}
paused={paused}
fullscreen={this.state.fullScreen}
resizeMode={"contain"}
......
......@@ -7376,6 +7376,11 @@ react-native-collapsible-header-views@^1.0.2:
dependencies:
fast-memoize "^2.5.1"
react-native-convert-ph-asset@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/react-native-convert-ph-asset/-/react-native-convert-ph-asset-1.0.3.tgz#af299a6a3f7270b55c3bdfe472a40cea02c93c80"
integrity sha512-qdTrUsDlxOvC7KnamjjRnxaijr3myfOwzbYR0vyTl0BjPT25jkCQ9a4Q26egRdy8uqiw7mqB1ZhG5OohwnpsOg==
react-native-crypto@^2.0.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/react-native-crypto/-/react-native-crypto-2.2.0.tgz#c999ed7c96064f830e1f958687f53d0c44025770"
......
Please register or to comment