diff --git a/src/screens/Production/PlaybackDownload.js b/src/screens/Production/PlaybackDownload.js index 9620881..48dc8ca 100644 --- a/src/screens/Production/PlaybackDownload.js +++ b/src/screens/Production/PlaybackDownload.js @@ -2,6 +2,8 @@ import React, { useEffect, useMemo, useRef, useState } from 'react' import { Image, Platform, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native' import { MaterialCommunityIcons } from '@expo/vector-icons' import useMinuit from 'react-native-minuit/src/hooks/useMinuit.js' +import * as FileSystem from 'expo-file-system' +import { shareAsync } from 'expo-sharing' import { background } from '../../assets' import FullscreenIntroVideo from '../../components/FullscreenIntroVideo' import BorderGradientButton from '../../components/BorderGradientButton' @@ -24,51 +26,70 @@ import { Image as ExpoImage } from 'expo-image' import SubscriptionConfirmModal from '../../components/SubscriptionConfirmModal' import { isWeb } from '../../hooks/useLayoutType' -const triggerWebDownload = async (url, title) => { - if (Platform.OS !== 'web') return +const triggerDownload = async (url, title) => { if (!url) return - if (typeof document === 'undefined') return const baseName = (title || 'Playback').toString().trim() || 'Playback' const sanitized = baseName.replace(/[\\/:*?"<>|]/g, '-') const extension = guessExtension(url) || 'mp4' const filename = `${sanitized}.${extension}` - try { - const response = await fetch(url) - if (!response.ok || response.type === 'opaque') { - throw new Error(`download_failed_${response.status || 'opaque'}`) - } - const blob = await response.blob() - const blobUrl = URL.createObjectURL(blob) - const anchor = document.createElement('a') - anchor.href = blobUrl - anchor.download = filename - anchor.rel = 'noopener noreferrer' - document.body.appendChild(anchor) - anchor.click() - document.body.removeChild(anchor) - URL.revokeObjectURL(blobUrl) - } catch (error) { - console.log('[PlaybackDownload] web download fallback', { - message: error?.message, - }) + + // WEB + if (Platform.OS === 'web') { + if (typeof document === 'undefined') return try { + const response = await fetch(url) + if (!response.ok || response.type === 'opaque') { + throw new Error(`download_failed_${response.status || 'opaque'}`) + } + const blob = await response.blob() + const blobUrl = URL.createObjectURL(blob) const anchor = document.createElement('a') - anchor.href = url + anchor.href = blobUrl anchor.download = filename anchor.rel = 'noopener noreferrer' - anchor.target = '_blank' document.body.appendChild(anchor) anchor.click() document.body.removeChild(anchor) - return - } catch (fallbackError) { - console.log('[PlaybackDownload] anchor fallback failed', { - message: fallbackError?.message, + // Revoke with delay to ensure browser captures it + setTimeout(() => URL.revokeObjectURL(blobUrl), 150) + } catch (error) { + console.log('[PlaybackDownload] web download fallback', { + message: error?.message, }) + // First fallback: Anchor with _blank + try { + const anchor = document.createElement('a') + anchor.href = url + anchor.download = filename + anchor.rel = 'noopener noreferrer' + anchor.target = '_blank' + document.body.appendChild(anchor) + anchor.click() + document.body.removeChild(anchor) + } catch (fallbackError) { + console.log('[PlaybackDownload] anchor fallback failed', { + message: fallbackError?.message, + }) + // Ultimate fallback: direct window open + try { + window.open(url, '_blank', 'noopener,noreferrer') + } catch { } + } } + } else { + // NATIVE (Android / iOS) try { - window.open(url, '_blank', 'noopener,noreferrer') - } catch {} + if (FileSystem?.downloadAsync) { + const fileUri = FileSystem.documentDirectory + filename + const { uri: localUri } = await FileSystem.downloadAsync(url, fileUri) + console.log('Finished downloading to ', localUri) + if (shareAsync) { + await shareAsync(localUri) + } + } + } catch (e) { + console.error(e) + } } } @@ -156,17 +177,13 @@ const PlaybackDownload = ({ route }) => { } }, [project?.playbackUrl]) - useEffect(() => { - return () => { - releaseBlobUrl(uri || null) - } - }, [uri]) + const handleDownloadUri = async () => { if (isPublishing) return if (action === 'playback' && project?.id) { if (pendingPlaybackUrl) { - await triggerWebDownload(pendingPlaybackUrl, project?.title) + await triggerDownload(pendingPlaybackUrl, project?.title) return } // Publication du playback @@ -216,7 +233,7 @@ const PlaybackDownload = ({ route }) => { type: 'success', text: 'Playback prêt à télécharger', }) - await triggerWebDownload(resultURI, project?.title) + await triggerDownload(resultURI, project?.title) if (tempSourcePath) { try { await firebase.storage().ref(tempSourcePath).delete() @@ -243,7 +260,7 @@ const PlaybackDownload = ({ route }) => { if (tempSourcePath) { try { await firebase.storage().ref(tempSourcePath).delete() - } catch {} + } catch { } } setTooltip({ type: 'error', @@ -369,6 +386,11 @@ const PlaybackDownload = ({ route }) => { const continueLabel = hasActiveSubscription ? 'Publier' : 'Publier sans générer de revenus' return ( <> + setIsAfterPlaybackVideoVisible(false)} + />