Skip to content

Commit 16ee341

Browse files
Merge pull request OwnTube-tv#350 from mykhailodanilenko/feature/live-streams-adjustments
Live video info refresh + privacy policy adjustments
2 parents 57f8da7 + 09c0819 commit 16ee341

File tree

17 files changed

+335
-226
lines changed

17 files changed

+335
-226
lines changed

OwnTube.tv/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ EXPO_APP_THEME_COLOR='#000000'
2222
EXPO_APP_BG_COLOR='#FFFFFF'
2323
EXPO_APP_DESCRIPTION=A nice PeerTube client app.
2424
EXPO_PUBLIC_FOOTER_LOGO=https://cdn-icons-png.flaticon.com/512/1170/1170688.png
25+
EXPO_PUBLIC_PROVIDER_LEGAL_ENTITY='OwnTube Nordic AB'
26+
EXPO_PUBLIC_PROVIDER_LEGAL_EMAIL='legal@owntube.tv'
27+
EXPO_PUBLIC_PROVIDER_CONTACT_EMAIL='hello@owntube.tv'

OwnTube.tv/api/queries/videos.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useLocalSearchParams } from "expo-router";
22
import { RootStackParams } from "../../app/_layout";
3-
import { useInfiniteQuery, useMutation, useQuery } from "@tanstack/react-query";
3+
import { Query, useInfiniteQuery, useMutation, useQuery } from "@tanstack/react-query";
44
import { GetVideosVideo } from "../models";
55
import { ApiServiceImpl } from "../peertubeVideosApi";
66
import { VideosCommonQuery, Video } from "@peertube/peertube-types";
@@ -15,11 +15,13 @@ export const useGetVideosQuery = <TResult = GetVideosVideo[]>({
1515
select,
1616
params,
1717
uniqueQueryKey,
18+
refetchInterval,
1819
}: {
1920
enabled?: boolean;
2021
select?: (queryReturn: { data: GetVideosVideo[]; total: number }) => { data: TResult; total: number };
2122
params?: VideosCommonQuery;
2223
uniqueQueryKey?: string;
24+
refetchInterval?: number;
2325
}) => {
2426
const { backend } = useLocalSearchParams<RootStackParams["index"]>();
2527

@@ -35,6 +37,7 @@ export const useGetVideosQuery = <TResult = GetVideosVideo[]>({
3537
enabled: enabled && !!backend,
3638
refetchOnWindowFocus: false,
3739
select,
40+
refetchInterval,
3841
retry,
3942
});
4043
};
@@ -73,6 +76,8 @@ export const useInfiniteVideosQuery = (
7376
});
7477
};
7578

79+
const LIVE_REFETCH_INTERVAL = 10_000;
80+
7681
export const useGetVideoQuery = <TResult = Video>({
7782
id,
7883
select,
@@ -95,6 +100,9 @@ export const useGetVideoQuery = <TResult = Video>({
95100
},
96101
refetchOnWindowFocus: false,
97102
enabled: !!backend && !!id && enabled,
103+
refetchInterval: (query: Query<Video>) => {
104+
return query.state.data?.isLive ? LIVE_REFETCH_INTERVAL : 0;
105+
},
98106
select,
99107
retry,
100108
});

OwnTube.tv/app/(home)/privacy.tsx

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import Head from "expo-router/head";
2+
import { Platform, ScrollView, StyleSheet } from "react-native";
3+
import { Button, Separator, Typography } from "../../components";
4+
import { Link, useRouter } from "expo-router";
5+
import { spacing } from "../../theme";
6+
import { useSafeAreaInsets } from "react-native-safe-area-context";
7+
import { ROUTES } from "../../types";
8+
import { Spacer } from "../../components/shared/Spacer";
9+
import { colors } from "../../colors";
10+
11+
export default function privacy() {
12+
const { top } = useSafeAreaInsets();
13+
const router = useRouter();
14+
const handleBackButton = () => {
15+
if (router.canGoBack()) {
16+
router.back();
17+
} else {
18+
router.navigate(ROUTES.HOME);
19+
}
20+
};
21+
22+
return (
23+
<>
24+
{Platform.select({
25+
default: null,
26+
web: (
27+
<Head>
28+
<meta charSet="utf-8" />
29+
<meta name="viewport" content="width=device-width" />
30+
<title>Privacy Policy</title>
31+
</Head>
32+
),
33+
})}
34+
<ScrollView style={{ padding: spacing.xl, paddingTop: top }}>
35+
{Platform.select({
36+
web: <Spacer height={spacing.xl} />,
37+
default: (
38+
<Button
39+
onPress={handleBackButton}
40+
contrast="high"
41+
icon="Arrow-Left"
42+
style={{ alignSelf: "flex-start", marginBottom: spacing.xl }}
43+
/>
44+
),
45+
})}
46+
<Typography fontWeight="Bold">Privacy Policy</Typography>
47+
<Typography>
48+
This privacy policy applies to this app (hereafter referred to as &#34;Application&#34;) for mobile/TV devices
49+
that is created by {process.env.EXPO_PUBLIC_PROVIDER_LEGAL_ENTITY || "OwnTube Nordic AB"} (hereafter referred
50+
to as &#34;Service Provider&#34;) as a free service. This service is provided &#34;AS IS&#34;.
51+
</Typography>
52+
<Typography>{"\n"}</Typography>
53+
<Typography fontWeight="Bold">What information does the Application obtain and how is it used?</Typography>
54+
<Typography>
55+
The Application does not collect, store, or process any personal data. No registration is required, and no
56+
analytics, tracking, or user profiling is performed.
57+
</Typography>
58+
<Typography>{"\n"}</Typography>
59+
<Typography fontWeight="Bold">
60+
Does the Application collect precise real-time location information of the device?
61+
</Typography>
62+
<Typography>This Application does not collect or use precise location data from your mobile device.</Typography>
63+
<Typography>{"\n"}</Typography>
64+
<Typography fontWeight="Bold">
65+
Do third parties see and/or have access to personal information obtained by the Application?
66+
</Typography>
67+
<Typography>
68+
Since the Application does not collect any personal information, no data is shared with third parties.
69+
</Typography>
70+
<Typography>{"\n"}</Typography>
71+
<Typography fontWeight="Bold">Children</Typography>
72+
<Typography>
73+
The Application is intended for users aged 13 and older. We do not knowingly collect any personal data from
74+
children under 13. If you believe a child has provided personal data, please contact us at{" "}
75+
<Link
76+
style={styles.link}
77+
href={`mailto:${process.env.EXPO_PUBLIC_PROVIDER_LEGAL_EMAIL || "legal@owntube.tv"}`}
78+
>
79+
{process.env.EXPO_PUBLIC_PROVIDER_LEGAL_EMAIL || "legal@owntube.tv"}
80+
</Link>{" "}
81+
so that we can take appropriate action.
82+
</Typography>
83+
<Typography>{"\n"}</Typography>
84+
<Typography fontWeight="Bold">Security</Typography>
85+
<Typography>
86+
Since the Application does not collect or store user data, there are no security risks related to personal
87+
information.
88+
</Typography>
89+
<Typography>{"\n"}</Typography>
90+
<Typography fontWeight="Bold">Changes</Typography>
91+
<Typography>
92+
This Privacy Policy may be updated from time to time for any reason. The Service Provider will notify you of
93+
any changes to this Privacy Policy by updating this page. You are advised to consult this Privacy Policy
94+
regularly for any updates, as continued use is deemed approval of all changes.
95+
</Typography>
96+
<Typography>{"\n"}</Typography>
97+
<Typography>This privacy policy is effective as of 2025-01-21</Typography>
98+
<Typography>{"\n"}</Typography>
99+
<Typography fontWeight="Bold">Contact Us</Typography>
100+
<Typography>
101+
If you have any questions regarding privacy while using the Application, please contact the Service Provider
102+
via email at{" "}
103+
<Link
104+
style={styles.link}
105+
href={`mailto:${process.env.EXPO_PUBLIC_PROVIDER_CONTACT_EMAIL || "hello@owntube.tv"}`}
106+
>
107+
{process.env.EXPO_PUBLIC_PROVIDER_CONTACT_EMAIL || "hello@owntube.tv"}
108+
</Link>
109+
.
110+
</Typography>
111+
<Spacer height={spacing.lg} />
112+
<Separator />
113+
<Spacer height={spacing.lg} />
114+
<Typography>
115+
This privacy policy was inspired by the{" "}
116+
<Link
117+
href="https://github.com/nisrulz/app-privacy-policy-generator"
118+
target="_blank"
119+
rel="noopener noreferrer"
120+
style={styles.link}
121+
>
122+
nisrulz/app-privacy-policy-generator
123+
</Link>{" "}
124+
GitHub project.
125+
</Typography>
126+
</ScrollView>
127+
</>
128+
);
129+
}
130+
131+
const styles = StyleSheet.create({
132+
link: { color: colors.blue, textDecorationLine: "underline" },
133+
});

OwnTube.tv/components/InfoFooter.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { BuildInfo } from "./BuildInfo";
66
import { Typography } from "./Typography";
77
import { useTranslation } from "react-i18next";
88
import { Image } from "react-native";
9+
import { Link } from "expo-router";
10+
import { ROUTES } from "../types";
911

1012
interface InfoFooterProps {
1113
showBuildInfo?: boolean;
@@ -40,6 +42,11 @@ export const InfoFooter = ({ showBuildInfo }: InfoFooterProps) => {
4042
)}
4143
</View>
4244
)}
45+
<Link href={ROUTES.PRIVACY}>
46+
<Typography style={{ textDecorationLine: "underline" }} fontSize={"sizeXS"} color={colors.themeDesaturated500}>
47+
{t("privacyPolicy")}
48+
</Typography>
49+
</Link>
4350
</View>
4451
);
4552
};

OwnTube.tv/components/VideoControlsOverlay/VideoControlsOverlay.tsx

Lines changed: 74 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export interface VideoControlsOverlayProps {
6464
selectedCCLang?: string;
6565
setSelectedCCLang?: (lang: string) => void;
6666
isLiveVideo?: boolean;
67+
isWaitingForLive: boolean;
6768
}
6869

6970
const VideoControlsOverlay = ({
@@ -107,6 +108,7 @@ const VideoControlsOverlay = ({
107108
selectedCCLang,
108109
setSelectedCCLang,
109110
isLiveVideo,
111+
isWaitingForLive,
110112
}: PropsWithChildren<VideoControlsOverlayProps>) => {
111113
const {
112114
isSeekBarFocused,
@@ -228,72 +230,81 @@ const VideoControlsOverlay = ({
228230
<ViewOnSiteLink site={videoLinkProps?.backend} url={videoLinkProps?.url} />
229231
)}
230232
</View>
231-
{!isLiveVideo && (
232-
<Pressable
233-
accessible={false}
234-
onHoverIn={() => setIsSeekBarFocused(true)}
235-
onHoverOut={() => setIsSeekBarFocused(false)}
236-
style={styles.scrubBarContainer}
237-
>
238-
<ScrubBar
239-
isExpanded={isSeekBarFocused}
240-
variant="seek"
241-
length={duration}
242-
onDrag={handleJumpTo}
243-
percentageAvailable={percentageAvailable}
244-
percentagePosition={percentagePosition}
245-
/>
246-
</Pressable>
247-
)}
248-
<View style={styles.bottomRowContainer}>
249-
<View style={styles.bottomRowControlsContainer}>
250-
{!isMobile && (
251-
<>
252-
<PlayerButton onPress={shouldReplay ? handleReplay : handlePlayPause} icon={centralIconName} />
253-
{!isLiveVideo && (
254-
<>
255-
<PlayerButton onPress={() => handleRW(15)} icon="Rewind-15" />
256-
<PlayerButton onPress={() => handleFF(30)} icon="Fast-forward-30" />
257-
</>
258-
)}
259-
<VolumeControl
260-
setVolume={handleVolumeControl}
261-
volume={volume}
262-
isMute={isMute}
263-
toggleMute={toggleMute}
264-
/>
265-
</>
266-
)}
233+
{isWaitingForLive ? null : (
234+
<>
267235
{!isLiveVideo && (
268-
<Typography
269-
fontSize="sizeXS"
270-
fontWeight="Medium"
271-
style={[styles.timingContainer, { paddingLeft: isMobile ? spacing.sm : null }]}
272-
color={colors.white94}
236+
<Pressable
237+
accessible={false}
238+
onHoverIn={() => setIsSeekBarFocused(true)}
239+
onHoverOut={() => setIsSeekBarFocused(false)}
240+
style={styles.scrubBarContainer}
273241
>
274-
{`${getHumanReadableDuration(position * 1000)} / ${getHumanReadableDuration(duration * 1000)}`}
275-
</Typography>
276-
)}
277-
</View>
278-
<View style={styles.functionButtonsContainer}>
279-
{castState !== "airPlay" && (
280-
<GoogleCastButton
281-
isChromeCastAvailable={isChromeCastAvailable}
282-
handleLoadGoogleCastMedia={handleLoadGoogleCastMedia}
283-
/>
284-
)}
285-
{castState !== "chromecast" && <AvRoutePickerButton isWebAirPlayAvailable={isWebAirPlayAvailable} />}
286-
{isCCAvailable && (
287-
<PlayerButton
288-
color={isCCVisible ? undefined : colors.white25}
289-
icon="Closed-Captions"
290-
onPress={handleToggleCC}
291-
/>
242+
<ScrubBar
243+
isExpanded={isSeekBarFocused}
244+
variant="seek"
245+
length={duration}
246+
onDrag={handleJumpTo}
247+
percentageAvailable={percentageAvailable}
248+
percentagePosition={percentagePosition}
249+
/>
250+
</Pressable>
292251
)}
293-
<PlayerButton icon="Settings" onPress={() => setIsSettingsMenuVisible((cur) => !cur)} />
294-
<PlayerButton onPress={toggleFullscreen} icon={`Fullscreen${isFullscreen ? "-Exit" : ""}`} />
295-
</View>
296-
</View>
252+
<View style={styles.bottomRowContainer}>
253+
<View style={styles.bottomRowControlsContainer}>
254+
{!isMobile && (
255+
<>
256+
<PlayerButton
257+
onPress={shouldReplay ? handleReplay : handlePlayPause}
258+
icon={centralIconName}
259+
/>
260+
{!isLiveVideo && (
261+
<>
262+
<PlayerButton onPress={() => handleRW(15)} icon="Rewind-15" />
263+
<PlayerButton onPress={() => handleFF(30)} icon="Fast-forward-30" />
264+
</>
265+
)}
266+
<VolumeControl
267+
setVolume={handleVolumeControl}
268+
volume={volume}
269+
isMute={isMute}
270+
toggleMute={toggleMute}
271+
/>
272+
</>
273+
)}
274+
{!isLiveVideo && (
275+
<Typography
276+
fontSize="sizeXS"
277+
fontWeight="Medium"
278+
style={[styles.timingContainer, { paddingLeft: isMobile ? spacing.sm : null }]}
279+
color={colors.white94}
280+
>
281+
{`${getHumanReadableDuration(position * 1000)} / ${getHumanReadableDuration(duration * 1000)}`}
282+
</Typography>
283+
)}
284+
</View>
285+
<View style={styles.functionButtonsContainer}>
286+
{castState !== "airPlay" && (
287+
<GoogleCastButton
288+
isChromeCastAvailable={isChromeCastAvailable}
289+
handleLoadGoogleCastMedia={handleLoadGoogleCastMedia}
290+
/>
291+
)}
292+
{castState !== "chromecast" && (
293+
<AvRoutePickerButton isWebAirPlayAvailable={isWebAirPlayAvailable} />
294+
)}
295+
{isCCAvailable && (
296+
<PlayerButton
297+
color={isCCVisible ? undefined : colors.white25}
298+
icon="Closed-Captions"
299+
onPress={handleToggleCC}
300+
/>
301+
)}
302+
<PlayerButton icon="Settings" onPress={() => setIsSettingsMenuVisible((cur) => !cur)} />
303+
<PlayerButton onPress={toggleFullscreen} icon={`Fullscreen${isFullscreen ? "-Exit" : ""}`} />
304+
</View>
305+
</View>
306+
</>
307+
)}
297308
</LinearGradient>
298309
</Animated.View>
299310
</View>

0 commit comments

Comments
 (0)