Skip to content

Commit 99e78bb

Browse files
Merge pull request OwnTube-tv#403 from mykhailodanilenko/feature/download-management-fixes
2 parents c59937d + 6de72a0 commit 99e78bb

18 files changed

Lines changed: 99 additions & 131 deletions

File tree

OwnTube.tv/app.config.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,6 @@ export default {
107107
},
108108
],
109109
"expo-asset",
110-
[
111-
"expo-media-library",
112-
{
113-
photosPermission: `Allow ${process.env.EXPO_PUBLIC_APP_NAME || "OwnTube.tv"} to access your media library.`,
114-
savePhotosPermission: `Allow ${process.env.EXPO_PUBLIC_APP_NAME || "OwnTube.tv"} to save videos.`,
115-
preventAutomaticLimitedAccessAlert: true,
116-
},
117-
],
118110
"react-native-google-cast",
119111
[
120112
"./plugins/withReleaseSigningConfig.js",

OwnTube.tv/components/DeviceCapabilities/DeviceCapabilities.tsx

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { borderRadius } from "../../theme";
99
import { BuildInfo } from "../BuildInfo";
1010
import build_info from "../../build-info.json";
1111
import { useAuthSessionStore } from "../../store";
12+
import { format } from "date-fns";
13+
import { useMemo } from "react";
1214

1315
const CapabilityKeyValuePair = ({ label, value }: { label: string; value: string }) => {
1416
const { colors } = useTheme();
@@ -31,31 +33,48 @@ const DeviceCapabilities = () => {
3133

3234
const { colors } = useTheme();
3335
const { t } = useTranslation();
36+
const authInfo = useMemo(
37+
() =>
38+
session
39+
? {
40+
backend: session.backend,
41+
email: session.email,
42+
twoFactorEnabled: session.twoFactorEnabled,
43+
sessionCreatedAt: session.sessionCreatedAt,
44+
sessionUpdatedAt: session.sessionUpdatedAt,
45+
sessionExpired: session.sessionExpired,
46+
accessTokenIssuedAt: session.accessTokenIssuedAt,
47+
refreshTokenIssuedAt: session.refreshTokenIssuedAt,
48+
userInfoUpdatedAt: session.userInfoUpdatedAt,
49+
}
50+
: null,
51+
[session],
52+
);
3453

3554
const handleCopyToClipboard = async () => {
3655
const buildInfo = process.env.EXPO_PUBLIC_HIDE_GIT_DETAILS
3756
? { BUILD_TIMESTAMP: build_info.BUILD_TIMESTAMP }
3857
: build_info;
3958

40-
const authInfo = session
41-
? {
42-
backend: session.backend,
43-
email: session.email,
44-
twoFactorEnabled: session.twoFactorEnabled,
45-
sessionCreatedAt: session.sessionCreatedAt,
46-
sessionUpdatedAt: session.sessionUpdatedAt,
47-
sessionExpired: session.sessionExpired,
48-
accessTokenIssuedAt: session.accessTokenIssuedAt,
49-
refreshTokenIssuedAt: session.refreshTokenIssuedAt,
50-
userInfoUpdatedAt: session.userInfoUpdatedAt,
51-
}
52-
: null;
53-
5459
await Clipboard.setStringAsync(
5560
JSON.stringify({ buildInfo, ...deviceCapabilities, ...(authInfo ? { authInfo } : {}) }),
5661
);
5762
};
5863

64+
const currentAuthText = useMemo(() => {
65+
if (!authInfo) return "";
66+
67+
return t("currentAuthText", {
68+
userName: authInfo.email,
69+
backend: authInfo.backend,
70+
_2fa: authInfo.twoFactorEnabled ? t("_2faOn") : t("_2faOff"),
71+
sessionStartedAt: format(authInfo.sessionCreatedAt, "yyyy-MM-dd HH:mm"),
72+
sessionUpdatedAt: format(authInfo.sessionUpdatedAt, "yyyy-MM-dd HH:mm"),
73+
expiredText: authInfo.sessionExpired ? t("sessionExpired") : t("sessionActive"),
74+
refreshTokenIssuedAt: format(authInfo.refreshTokenIssuedAt, "yyyy-MM-dd HH:mm"),
75+
});
76+
}, [authInfo, t]);
77+
5978
return (
6079
<View style={{ backgroundColor: colors.theme50 }}>
6180
<View style={styles.modalHeader}>
@@ -73,6 +92,7 @@ const DeviceCapabilities = () => {
7392
</Typography>
7493
<BuildInfo />
7594
</View>
95+
{authInfo && <CapabilityKeyValuePair label={t("currentAuth")} value={currentAuthText} />}
7696
<CapabilityKeyValuePair label={t("playerImpl")} value={deviceCapabilities.playerImplementation} />
7797
<CapabilityKeyValuePair label={t("deviceType")} value={deviceCapabilities.deviceType} />
7898
<CapabilityKeyValuePair label={t("operatingSystem")} value={deviceCapabilities.OS} />

OwnTube.tv/components/VideoContextMenu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const VideoContextMenu: React.FC<VideoContextMenuProps> = ({ handleOpenSe
1616
const { t } = useTranslation();
1717

1818
return (
19-
<TVFocusGuideHelper style={[styles.container, { backgroundColor: colors.black80 }]}>
19+
<TVFocusGuideHelper style={[styles.container, { borderColor: colors.white25, backgroundColor: colors.black80 }]}>
2020
<Setting isSubmenuAvailable={false} onPress={handleOpenSettings} name={t("deviceCapabilityInfoTitle")} />
2121
{!!handleDownload && <Setting isSubmenuAvailable={false} onPress={handleDownload} name={t("downloadVideo")} />}
2222
</TVFocusGuideHelper>
@@ -26,8 +26,8 @@ export const VideoContextMenu: React.FC<VideoContextMenuProps> = ({ handleOpenSe
2626
const styles = StyleSheet.create({
2727
container: {
2828
borderRadius: borderRadius.radiusMd,
29+
borderWidth: 0.5,
2930
paddingVertical: spacing.sm,
30-
width: 256,
3131
},
3232
});
3333

OwnTube.tv/components/VideoControlsOverlay/VideoControlsOverlay.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ const VideoControlsOverlay = ({
203203
setIsContextMenuVisible(false);
204204
}}
205205
handleDownload={
206-
isDownloadAvailable
206+
isDownloadAvailable && !isLiveVideo
207207
? () => {
208208
handleDownload();
209209
setIsContextMenuVisible(false);

OwnTube.tv/components/shared/Picker/Picker.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ const Picker = (props: PickerSelectProps) => {
3939
inputIOS: textStyle,
4040
inputAndroidContainer: containerStyle,
4141
inputAndroid: { ...textStyle, fontWeight: "500" },
42+
iconContainer: {
43+
pointerEvents: "none",
44+
},
4245
...props.style,
4346
}}
4447
Icon={() => (
45-
<View
46-
pointerEvents="none"
47-
style={[styles.chevronContainer, { transform: [{ rotate: isPickerOpen ? "180deg" : "0deg" }] }]}
48-
>
48+
<View style={[styles.chevronContainer, { transform: [{ rotate: isPickerOpen ? "180deg" : "0deg" }] }]}>
4949
<IcoMoonIcon name="Chevron" size={24} color={colors.theme500} />
5050
</View>
5151
)}

OwnTube.tv/hooks/useDownloadVideo/useDownloadVideo.ts

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,29 @@
1-
import { Directory, Paths, File } from "expo-file-system/next";
21
import { useGlobalSearchParams } from "expo-router";
32
import { useState, useMemo, useEffect } from "react";
4-
import { useTranslation } from "react-i18next";
5-
import Toast from "react-native-toast-message";
63
import { useGetVideoQuery } from "../../api";
74
import { RootStackParams } from "../../app/_layout";
85
import { useFullScreenModalContext } from "../../contexts";
96
import { ROUTES } from "../../types";
10-
import { formatFileSize, sanitizeFileName } from "../../utils";
11-
import * as MediaLibrary from "expo-media-library";
7+
import { formatFileSize } from "../../utils";
8+
import { Linking } from "react-native";
129

1310
const useDownloadVideo = () => {
1411
const { toggleModal } = useFullScreenModalContext();
15-
const { t } = useTranslation();
1612
const params = useGlobalSearchParams<RootStackParams[ROUTES.VIDEO]>();
1713
const [selectedFile, setSelectedFile] = useState<string>();
1814

1915
const { data: videoData } = useGetVideoQuery({ id: params?.id });
2016

2117
const pickerOptions = useMemo(() => {
22-
if (videoData?.files && Number(videoData?.files?.length) > 0) {
23-
return videoData.files.map((file) => ({
18+
if (videoData?.streamingPlaylists && Number(videoData?.streamingPlaylists?.length) > 0) {
19+
return videoData.streamingPlaylists[0].files.map((file) => ({
2420
label: `${file.resolution.label} (${formatFileSize(file.size)})`,
2521
value: file.fileDownloadUrl,
2622
}));
2723
}
2824

29-
if (videoData?.streamingPlaylists && Number(videoData?.streamingPlaylists?.length) > 0) {
30-
return videoData.streamingPlaylists[0].files.map((file) => ({
25+
if (videoData?.files && Number(videoData?.files?.length) > 0) {
26+
return videoData.files.map((file) => ({
3127
label: `${file.resolution.label} (${formatFileSize(file.size)})`,
3228
value: file.fileDownloadUrl,
3329
}));
@@ -37,42 +33,23 @@ const useDownloadVideo = () => {
3733
}, [videoData]);
3834

3935
useEffect(() => {
40-
if (pickerOptions.length > 0) {
41-
setSelectedFile(pickerOptions[0].value);
36+
if (pickerOptions && pickerOptions.length > 1) {
37+
const lowestQualityOption = pickerOptions.at(pickerOptions.at(-1)?.label.startsWith("Audio only") ? -2 : -1);
38+
39+
if (lowestQualityOption) {
40+
setSelectedFile(lowestQualityOption.value);
41+
}
4242
}
4343
}, [pickerOptions]);
4444

4545
const handleDownloadFile = async () => {
4646
if (!selectedFile) {
4747
return;
4848
}
49-
const destination = new Directory(Paths.cache, "videos");
50-
51-
try {
52-
if (!destination.exists) {
53-
destination.create();
54-
}
55-
56-
toggleModal(false);
57-
Toast.show({ type: "info", text1: t("downloadStarted") });
58-
59-
const fileName = sanitizeFileName(videoData?.name) || "video";
60-
const fileDownloadUrl = destination.uri + `/${fileName || "video"}.mp4`;
6149

62-
const output = await File.downloadFileAsync(selectedFile, new File(fileDownloadUrl));
50+
toggleModal(false);
6351

64-
await MediaLibrary.requestPermissionsAsync(true);
65-
66-
await MediaLibrary.saveToLibraryAsync(output.uri);
67-
68-
output.delete();
69-
70-
const ellipsizedFileName = fileName.length > 10 ? fileName.slice(0, 10) + "..." : fileName;
71-
Toast.show({ type: "info", text1: t("downloadComplete", { fileName: ellipsizedFileName }) });
72-
} catch (error) {
73-
Toast.show({ type: "info", text1: t("downloadError"), props: { isError: true } });
74-
console.error("Error downloading file:", error);
75-
}
52+
Linking.openURL(selectedFile);
7653
};
7754

7855
return { handleDownloadFile, selectedFile, setSelectedFile, pickerOptions };

OwnTube.tv/hooks/useDownloadVideo/useDownloadVideo.web.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ const useDownloadVideo = () => {
1212
const { data: videoData } = useGetVideoQuery({ id: params?.id });
1313

1414
const pickerOptions = useMemo(() => {
15-
if (videoData?.files && Number(videoData?.files?.length) > 0) {
16-
return videoData.files.map((file) => ({
15+
if (videoData?.streamingPlaylists && Number(videoData?.streamingPlaylists?.length) > 0) {
16+
return videoData.streamingPlaylists[0].files.map((file) => ({
1717
label: `${file.resolution.label} (${formatFileSize(file.size)})`,
1818
value: file.fileDownloadUrl,
1919
}));
2020
}
2121

22-
if (videoData?.streamingPlaylists && Number(videoData?.streamingPlaylists?.length) > 0) {
23-
return videoData.streamingPlaylists[0].files.map((file) => ({
22+
if (videoData?.files && Number(videoData?.files?.length) > 0) {
23+
return videoData.files.map((file) => ({
2424
label: `${file.resolution.label} (${formatFileSize(file.size)})`,
2525
value: file.fileDownloadUrl,
2626
}));
@@ -30,8 +30,12 @@ const useDownloadVideo = () => {
3030
}, [videoData]);
3131

3232
useEffect(() => {
33-
if (pickerOptions.length > 0) {
34-
setSelectedFile(pickerOptions[0].value);
33+
if (pickerOptions && pickerOptions.length > 1) {
34+
const lowestQualityOption = pickerOptions.at(pickerOptions.at(-1)?.label.startsWith("Audio only") ? -2 : -1);
35+
36+
if (lowestQualityOption) {
37+
setSelectedFile(lowestQualityOption.value);
38+
}
3539
}
3640
}, [pickerOptions]);
3741

OwnTube.tv/locales/en.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,5 +173,11 @@
173173
"download": "Download",
174174
"downloadStarted": "Your download has started, please wait...",
175175
"downloadComplete": "File {{fileName}} is downloaded!",
176-
"downloadError": "Download error"
176+
"downloadError": "Download error",
177+
"currentAuth": "Current authentication:",
178+
"currentAuthText": "User {{userName}} at {{backend}}, with two-factor {{_2fa}}, user session started at {{sessionStartedAt}}, updated at {{sessionUpdatedAt}}, {{expiredText}}, refresh token issued at {{refreshTokenIssuedAt}}",
179+
"_2faOff": "off",
180+
"_2faOn": "on",
181+
"sessionExpired": "expired",
182+
"sessionActive": "active"
177183
}

OwnTube.tv/locales/ru.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,5 +172,11 @@
172172
"downloadComplete": "Загрузка файла {{fileName}} завершена",
173173
"chooseQuality": "Выберите качество видео:",
174174
"download": "Скачать",
175-
"downloadError": "Ошибка загрузки"
175+
"downloadError": "Ошибка загрузки",
176+
"currentAuth": "Текущая сессия:",
177+
"currentAuthText": "Пользователь {{userName}} на {{backend}}, с {{_2fa}} двухфакторной аутентификацией, сессия создана в {{sessionStartedAt}}, обновлена в {{sessionUpdatedAt}}, {{expiredText}}, токен продления сессии выдан в {{refreshTokenIssuedAt}}",
178+
"_2faOn": "включенной",
179+
"_2faOff": "выключенной",
180+
"sessionExpired": "истекла",
181+
"sessionActive": "активна"
176182
}

OwnTube.tv/locales/sv.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,5 +173,11 @@
173173
"downloadVideo": "Ladda ner video",
174174
"chooseQuality": "Välj videokvalitet:",
175175
"download": "Ladda ner",
176-
"downloadError": "Nedladdningsfel"
176+
"downloadError": "Nedladdningsfel",
177+
"currentAuth": "Aktuell autentisering:",
178+
"_2faOn": "",
179+
"_2faOff": "av",
180+
"sessionActive": "aktiv",
181+
"sessionExpired": "utgången",
182+
"currentAuthText": "Användare {{userName}} vid {{backend}}, med tvåfaktor {{_2fa}}, session startad {{sessionStartedAt}}, uppdaterad {{sessionUpdatedAt}}, {{expiredText}}, refresh token utfärdad {{refreshTokenIssuedAt}}"
177183
}

0 commit comments

Comments
 (0)