@streamplace/components 0.9.9 → 0.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/badges/live.png +0 -0
- package/assets/badges/live_2x.png +0 -0
- package/assets/badges/mod.png +0 -0
- package/assets/badges/mod_2x.png +0 -0
- package/assets/badges/vip.png +0 -0
- package/assets/badges/vip_2x.png +0 -0
- package/dist/components/chat/badge.d.ts +10 -0
- package/dist/components/chat/badge.d.ts.map +1 -0
- package/dist/components/chat/badge.js +29 -0
- package/dist/components/chat/badge.js.map +1 -0
- package/dist/components/chat/chat-box.d.ts +5 -1
- package/dist/components/chat/chat-box.d.ts.map +1 -1
- package/dist/components/chat/chat-box.js +55 -50
- package/dist/components/chat/chat-box.js.map +1 -1
- package/dist/components/chat/chat-message.d.ts.map +1 -1
- package/dist/components/chat/chat-message.js +16 -11
- package/dist/components/chat/chat-message.js.map +1 -1
- package/dist/components/chat/chat.d.ts.map +1 -1
- package/dist/components/chat/chat.js +35 -41
- package/dist/components/chat/chat.js.map +1 -1
- package/dist/components/chat/emoji-suggestions.d.ts +7 -18
- package/dist/components/chat/emoji-suggestions.d.ts.map +1 -1
- package/dist/components/chat/emoji-suggestions.js +6 -2
- package/dist/components/chat/emoji-suggestions.js.map +1 -1
- package/dist/components/chat/system-message.d.ts.map +1 -1
- package/dist/components/chat/system-message.js +9 -1
- package/dist/components/chat/system-message.js.map +1 -1
- package/dist/components/chat/teleport-modal.d.ts +9 -0
- package/dist/components/chat/teleport-modal.d.ts.map +1 -0
- package/dist/components/chat/teleport-modal.js +148 -0
- package/dist/components/chat/teleport-modal.js.map +1 -0
- package/dist/components/chat/user-profile-card.d.ts +12 -0
- package/dist/components/chat/user-profile-card.d.ts.map +1 -0
- package/dist/components/chat/user-profile-card.js +136 -0
- package/dist/components/chat/user-profile-card.js.map +1 -0
- package/dist/components/dashboard/chat-panel.d.ts +3 -1
- package/dist/components/dashboard/chat-panel.d.ts.map +1 -1
- package/dist/components/dashboard/chat-panel.js +2 -2
- package/dist/components/dashboard/chat-panel.js.map +1 -1
- package/dist/components/dashboard/header.d.ts +1 -1
- package/dist/components/dashboard/header.d.ts.map +1 -1
- package/dist/components/dashboard/header.js +4 -0
- package/dist/components/dashboard/header.js.map +1 -1
- package/dist/components/dashboard/information-widget.d.ts.map +1 -1
- package/dist/components/dashboard/information-widget.js +13 -11
- package/dist/components/dashboard/information-widget.js.map +1 -1
- package/dist/components/mobile-player/fullscreen.d.ts.map +1 -1
- package/dist/components/mobile-player/fullscreen.js +2 -1
- package/dist/components/mobile-player/fullscreen.js.map +1 -1
- package/dist/components/mobile-player/fullscreen.native.d.ts.map +1 -1
- package/dist/components/mobile-player/fullscreen.native.js +3 -2
- package/dist/components/mobile-player/fullscreen.native.js.map +1 -1
- package/dist/components/mobile-player/ui/audio-only-overlay.d.ts +2 -0
- package/dist/components/mobile-player/ui/audio-only-overlay.d.ts.map +1 -0
- package/dist/components/mobile-player/ui/audio-only-overlay.js +29 -0
- package/dist/components/mobile-player/ui/audio-only-overlay.js.map +1 -0
- package/dist/components/mobile-player/ui/index.d.ts +1 -0
- package/dist/components/mobile-player/ui/index.d.ts.map +1 -1
- package/dist/components/mobile-player/ui/index.js +1 -0
- package/dist/components/mobile-player/ui/index.js.map +1 -1
- package/dist/components/mobile-player/ui/input.d.ts +1 -2
- package/dist/components/mobile-player/ui/input.d.ts.map +1 -1
- package/dist/components/mobile-player/ui/input.js +2 -2
- package/dist/components/mobile-player/ui/input.js.map +1 -1
- package/dist/components/mobile-player/ui/metrics.d.ts.map +1 -1
- package/dist/components/mobile-player/ui/metrics.js +20 -2
- package/dist/components/mobile-player/ui/metrics.js.map +1 -1
- package/dist/components/mobile-player/ui/viewer-context-menu.d.ts.map +1 -1
- package/dist/components/mobile-player/ui/viewer-context-menu.js +29 -1
- package/dist/components/mobile-player/ui/viewer-context-menu.js.map +1 -1
- package/dist/components/mobile-player/use-webrtc.d.ts +4 -2
- package/dist/components/mobile-player/use-webrtc.d.ts.map +1 -1
- package/dist/components/mobile-player/use-webrtc.js +89 -15
- package/dist/components/mobile-player/use-webrtc.js.map +1 -1
- package/dist/components/mobile-player/video-async.native.d.ts.map +1 -1
- package/dist/components/mobile-player/video-async.native.js +15 -5
- package/dist/components/mobile-player/video-async.native.js.map +1 -1
- package/dist/components/mobile-player/video.d.ts.map +1 -1
- package/dist/components/mobile-player/video.js +10 -7
- package/dist/components/mobile-player/video.js.map +1 -1
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +8 -0
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/hooks/useLivestreamInfo.d.ts +0 -2
- package/dist/hooks/useLivestreamInfo.d.ts.map +1 -1
- package/dist/hooks/useLivestreamInfo.js +13 -24
- package/dist/hooks/useLivestreamInfo.js.map +1 -1
- package/dist/hooks/useSegmentTiming.d.ts +1 -1
- package/dist/hooks/useSegmentTiming.d.ts.map +1 -1
- package/dist/hooks/useSegmentTiming.js +4 -0
- package/dist/hooks/useSegmentTiming.js.map +1 -1
- package/dist/i18n/i18n-loader.native.d.ts.map +1 -1
- package/dist/i18n/i18n-loader.native.js +13 -4
- package/dist/i18n/i18n-loader.native.js.map +1 -1
- package/dist/lib/slash-commands/teleport.d.ts +5 -1
- package/dist/lib/slash-commands/teleport.d.ts.map +1 -1
- package/dist/lib/slash-commands/teleport.js +57 -1
- package/dist/lib/slash-commands/teleport.js.map +1 -1
- package/dist/livestream-store/chat.d.ts +1 -0
- package/dist/livestream-store/chat.d.ts.map +1 -1
- package/dist/livestream-store/chat.js +10 -1
- package/dist/livestream-store/chat.js.map +1 -1
- package/dist/livestream-store/livestream-state.d.ts +2 -0
- package/dist/livestream-store/livestream-state.d.ts.map +1 -1
- package/dist/livestream-store/livestream-store.d.ts +1 -1
- package/dist/livestream-store/livestream-store.d.ts.map +1 -1
- package/dist/livestream-store/livestream-store.js +10 -1
- package/dist/livestream-store/livestream-store.js.map +1 -1
- package/dist/livestream-store/websocket-consumer.d.ts.map +1 -1
- package/dist/livestream-store/websocket-consumer.js +1 -0
- package/dist/livestream-store/websocket-consumer.js.map +1 -1
- package/dist/player-store/player-state.d.ts +1 -5
- package/dist/player-store/player-state.d.ts.map +1 -1
- package/dist/player-store/player-store.d.ts.map +1 -1
- package/dist/player-store/player-store.js +16 -5
- package/dist/player-store/player-store.js.map +1 -1
- package/dist/player-store/single-player-provider.d.ts +0 -2
- package/dist/player-store/single-player-provider.d.ts.map +1 -1
- package/dist/player-store/single-player-provider.js +0 -2
- package/dist/player-store/single-player-provider.js.map +1 -1
- package/dist/streamplace-store/stream.d.ts +4 -2
- package/dist/streamplace-store/stream.d.ts.map +1 -1
- package/dist/streamplace-store/stream.js +36 -74
- package/dist/streamplace-store/stream.js.map +1 -1
- package/locales/manifest.json +21 -1
- package/locales/ro-RO/common.ftl +74 -0
- package/locales/ro-RO/settings.ftl +233 -0
- package/locales/zh-Hans/common.ftl +57 -0
- package/locales/zh-Hans/settings.ftl +222 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/package.json +2 -2
- package/src/components/chat/badge.tsx +45 -0
- package/src/components/chat/chat-box.tsx +84 -54
- package/src/components/chat/chat-message.tsx +36 -21
- package/src/components/chat/chat.tsx +105 -88
- package/src/components/chat/emoji-suggestions.tsx +12 -21
- package/src/components/chat/system-message.tsx +12 -2
- package/src/components/chat/teleport-modal.tsx +310 -0
- package/src/components/chat/user-profile-card.tsx +284 -0
- package/src/components/dashboard/chat-panel.tsx +8 -0
- package/src/components/dashboard/header.tsx +7 -3
- package/src/components/dashboard/information-widget.tsx +15 -9
- package/src/components/mobile-player/fullscreen.native.tsx +3 -0
- package/src/components/mobile-player/fullscreen.tsx +2 -0
- package/src/components/mobile-player/ui/audio-only-overlay.tsx +48 -0
- package/src/components/mobile-player/ui/index.ts +1 -0
- package/src/components/mobile-player/ui/input.tsx +1 -5
- package/src/components/mobile-player/ui/metrics.tsx +17 -2
- package/src/components/mobile-player/ui/viewer-context-menu.tsx +40 -3
- package/src/components/mobile-player/use-webrtc.tsx +118 -17
- package/src/components/mobile-player/video-async.native.tsx +18 -5
- package/src/components/mobile-player/video.tsx +10 -7
- package/src/components/ui/dialog.tsx +8 -0
- package/src/hooks/useLivestreamInfo.ts +15 -24
- package/src/hooks/useSegmentTiming.tsx +7 -2
- package/src/i18n/i18n-loader.native.ts +9 -0
- package/src/lib/slash-commands/teleport.ts +68 -0
- package/src/livestream-store/chat.tsx +12 -0
- package/src/livestream-store/livestream-state.tsx +2 -0
- package/src/livestream-store/livestream-store.tsx +9 -1
- package/src/livestream-store/websocket-consumer.tsx +1 -0
- package/src/player-store/player-state.tsx +1 -7
- package/src/player-store/player-store.tsx +16 -7
- package/src/player-store/single-player-provider.tsx +0 -4
- package/src/streamplace-store/stream.tsx +42 -99
- package/node-compile-cache/v22.15.0-x64-92db9086-0/37be0eec +0 -0
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import { useLivestreamStore } from "../livestream-store";
|
|
3
3
|
import { usePlayerStore } from "../player-store";
|
|
4
|
-
import { useCreateStreamRecord } from "../streamplace-store";
|
|
4
|
+
import { useCreateStreamRecord, useEndLivestream } from "../streamplace-store";
|
|
5
5
|
|
|
6
6
|
export function useLivestreamInfo(url?: string) {
|
|
7
7
|
const ingest = usePlayerStore((x) => x.ingestConnectionState);
|
|
8
8
|
const profile = useLivestreamStore((x) => x.profile);
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const endLivestream = useEndLivestream();
|
|
10
|
+
const setLocalLivestreamURI = useLivestreamStore(
|
|
11
|
+
(x) => x.setLocalLivestreamURI,
|
|
12
|
+
);
|
|
14
13
|
const createStreamRecord = useCreateStreamRecord();
|
|
15
14
|
|
|
16
15
|
const [title, setTitle] = useState<string>("");
|
|
@@ -22,10 +21,11 @@ export function useLivestreamInfo(url?: string) {
|
|
|
22
21
|
if (title !== "") {
|
|
23
22
|
setRecordSubmitted(true);
|
|
24
23
|
// Create the livestream record with title and custom url if available
|
|
25
|
-
await createStreamRecord({
|
|
24
|
+
const { uri } = await createStreamRecord({
|
|
26
25
|
title,
|
|
27
26
|
canonicalUrl: url || undefined,
|
|
28
27
|
});
|
|
28
|
+
setLocalLivestreamURI(uri);
|
|
29
29
|
}
|
|
30
30
|
} catch (error) {
|
|
31
31
|
console.error("Error creating livestream:", error);
|
|
@@ -39,26 +39,19 @@ export function useLivestreamInfo(url?: string) {
|
|
|
39
39
|
keyboardHeight?: number,
|
|
40
40
|
closeKeyboard?: () => void,
|
|
41
41
|
) => {
|
|
42
|
-
if
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
setTimeout(() => {
|
|
50
|
-
handleSubmit();
|
|
51
|
-
}, 3000);
|
|
52
|
-
} else {
|
|
53
|
-
setIngestStarting(false);
|
|
54
|
-
setIngestLive(false);
|
|
55
|
-
}
|
|
42
|
+
// Optionally close keyboard if provided
|
|
43
|
+
if (closeKeyboard) closeKeyboard();
|
|
44
|
+
setShowCountdown(true);
|
|
45
|
+
// wait ~3 seconds before announcing
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
handleSubmit();
|
|
48
|
+
}, 3000);
|
|
56
49
|
};
|
|
57
50
|
|
|
58
51
|
// Stop the current broadcast
|
|
59
52
|
const toggleStopStream = () => {
|
|
60
53
|
console.log("Stopping stream...");
|
|
61
|
-
|
|
54
|
+
endLivestream();
|
|
62
55
|
};
|
|
63
56
|
|
|
64
57
|
return {
|
|
@@ -70,8 +63,6 @@ export function useLivestreamInfo(url?: string) {
|
|
|
70
63
|
setShowCountdown,
|
|
71
64
|
recordSubmitted,
|
|
72
65
|
setRecordSubmitted,
|
|
73
|
-
ingestStarting,
|
|
74
|
-
setIngestStarting,
|
|
75
66
|
handleSubmit,
|
|
76
67
|
toggleGoLive,
|
|
77
68
|
toggleStopStream,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { useLivestreamStore } from "../livestream-store";
|
|
2
|
+
import { useLivestream, useLivestreamStore } from "../livestream-store";
|
|
3
3
|
|
|
4
|
-
export type ConnectionQuality = "good" | "degraded" | "poor";
|
|
4
|
+
export type ConnectionQuality = "good" | "degraded" | "poor" | "pre-live";
|
|
5
5
|
|
|
6
6
|
function getLiveConnectionQuality(
|
|
7
7
|
timeBetweenSegments: number | null,
|
|
@@ -24,6 +24,7 @@ export function useSegmentTiming() {
|
|
|
24
24
|
const [segmentDeltas, setSegmentDeltas] = useState<number[]>([]);
|
|
25
25
|
const prevSegmentRef = useRef<any>();
|
|
26
26
|
const prevTimestampRef = useRef<number | null>(null);
|
|
27
|
+
const ls = useLivestream();
|
|
27
28
|
|
|
28
29
|
// Dummy state to force update every second
|
|
29
30
|
const [, setNow] = useState(Date.now());
|
|
@@ -84,5 +85,9 @@ export function useSegmentTiming() {
|
|
|
84
85
|
segmentDeltas.length,
|
|
85
86
|
);
|
|
86
87
|
|
|
88
|
+
if (!ls) {
|
|
89
|
+
to_ret.connectionQuality = "pre-live";
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
return to_ret;
|
|
88
93
|
}
|
|
@@ -10,6 +10,10 @@ import frFRCommon from "../../public/locales/fr-FR/common.json";
|
|
|
10
10
|
import frFRSettings from "../../public/locales/fr-FR/settings.json";
|
|
11
11
|
import ptBRCommon from "../../public/locales/pt-BR/common.json";
|
|
12
12
|
import ptBRSettings from "../../public/locales/pt-BR/settings.json";
|
|
13
|
+
import roROCommon from "../../public/locales/ro-RO/common.json";
|
|
14
|
+
import roROSettings from "../../public/locales/ro-RO/settings.json";
|
|
15
|
+
import zhHansCommon from "../../public/locales/zh-Hans/common.json";
|
|
16
|
+
import zhHansSettings from "../../public/locales/zh-Hans/settings.json";
|
|
13
17
|
import zhHantCommon from "../../public/locales/zh-Hant/common.json";
|
|
14
18
|
import zhHantSettings from "../../public/locales/zh-Hant/settings.json";
|
|
15
19
|
|
|
@@ -20,10 +24,14 @@ const translationMap: Record<string, any> = {
|
|
|
20
24
|
"pt-BR/settings": ptBRSettings,
|
|
21
25
|
"es-ES/common": esESCommon,
|
|
22
26
|
"es-ES/settings": esESSettings,
|
|
27
|
+
"zh-Hans/common": zhHansCommon,
|
|
28
|
+
"zh-Hans/settings": zhHansSettings,
|
|
23
29
|
"zh-Hant/common": zhHantCommon,
|
|
24
30
|
"zh-Hant/settings": zhHantSettings,
|
|
25
31
|
"fr-FR/common": frFRCommon,
|
|
26
32
|
"fr-FR/settings": frFRSettings,
|
|
33
|
+
"ro-RO/common": roROCommon,
|
|
34
|
+
"ro-RO/settings": roROSettings,
|
|
27
35
|
};
|
|
28
36
|
|
|
29
37
|
export async function loadTranslationData(
|
|
@@ -39,6 +47,7 @@ export async function loadTranslationData(
|
|
|
39
47
|
es: "es-ES",
|
|
40
48
|
zh: "zh-Hant",
|
|
41
49
|
fr: "fr-FR",
|
|
50
|
+
ro: "ro-RO",
|
|
42
51
|
}[locale] || locale;
|
|
43
52
|
|
|
44
53
|
const localeNamespaceKey = `${fullLocale}/${namespace}`;
|
|
@@ -21,16 +21,84 @@ export async function deleteTeleport(
|
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export async function createTeleport(
|
|
25
|
+
pdsAgent: StreamplaceAgent,
|
|
26
|
+
userDID: string,
|
|
27
|
+
targetHandle: string,
|
|
28
|
+
countdownSeconds: number,
|
|
29
|
+
setActiveTeleportUri?: (uri: string | null) => void,
|
|
30
|
+
): Promise<{ success: boolean; error?: string }> {
|
|
31
|
+
if (countdownSeconds < 5 || countdownSeconds > 300) {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: "Countdown must be between 5 seconds and 5 minutes",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let targetDID: string;
|
|
39
|
+
try {
|
|
40
|
+
const resolution = await pdsAgent.resolveHandle({
|
|
41
|
+
handle: targetHandle,
|
|
42
|
+
});
|
|
43
|
+
targetDID = resolution.data.did;
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
error: `Could not resolve handle: ${targetHandle}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (targetDID === userDID) {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: "You cannot teleport to yourself",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const startsAt = new Date(Date.now() + countdownSeconds * 1000).toISOString();
|
|
59
|
+
|
|
60
|
+
const record: PlaceStreamLiveTeleport.Record = {
|
|
61
|
+
$type: "place.stream.live.teleport",
|
|
62
|
+
streamer: targetDID,
|
|
63
|
+
startsAt,
|
|
64
|
+
countdownSeconds,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const result = await pdsAgent.com.atproto.repo.createRecord({
|
|
69
|
+
repo: userDID,
|
|
70
|
+
collection: "place.stream.live.teleport",
|
|
71
|
+
record,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (setActiveTeleportUri) {
|
|
75
|
+
setActiveTeleportUri(result.data.uri);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { success: true };
|
|
79
|
+
} catch (err) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: err instanceof Error ? err.message : "Failed to create teleport",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
24
87
|
export function registerTeleportCommand(
|
|
25
88
|
pdsAgent: StreamplaceAgent,
|
|
26
89
|
userDID: string,
|
|
27
90
|
setActiveTeleportUri?: (uri: string | null) => void,
|
|
91
|
+
onOpenModal?: () => void,
|
|
28
92
|
) {
|
|
29
93
|
const teleportHandler: SlashCommandHandler = async (
|
|
30
94
|
args,
|
|
31
95
|
rawInput,
|
|
32
96
|
): Promise<SlashCommandResult> => {
|
|
33
97
|
if (args.length === 0) {
|
|
98
|
+
if (onOpenModal) {
|
|
99
|
+
onOpenModal();
|
|
100
|
+
return { handled: true };
|
|
101
|
+
}
|
|
34
102
|
return {
|
|
35
103
|
handled: true,
|
|
36
104
|
error: "Usage: /teleport @handle.bsky.social [duration_seconds]",
|
|
@@ -155,6 +155,18 @@ export const useDeleteChatMessage = () => {
|
|
|
155
155
|
};
|
|
156
156
|
};
|
|
157
157
|
|
|
158
|
+
export const useAddSystemMessage = () => {
|
|
159
|
+
const store = getStoreFromContext();
|
|
160
|
+
return useCallback(
|
|
161
|
+
(message: ChatMessageViewHydrated) => {
|
|
162
|
+
const state = store.getState();
|
|
163
|
+
const newState = reduceChat(state, [message], []);
|
|
164
|
+
store.setState(newState);
|
|
165
|
+
},
|
|
166
|
+
[store],
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
158
170
|
const buildSortedChatList = (
|
|
159
171
|
chatIndex: { [key: string]: ChatMessageViewHydrated },
|
|
160
172
|
existingChatList: ChatMessageViewHydrated[],
|
|
@@ -32,6 +32,8 @@ export interface LivestreamState {
|
|
|
32
32
|
setModerationPermissions: (
|
|
33
33
|
permissions: PlaceStreamModerationPermission.Record[],
|
|
34
34
|
) => void;
|
|
35
|
+
localLivestreamURI: string | null;
|
|
36
|
+
setLocalLivestreamURI: (uri: string | null) => void;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
export interface LivestreamProblem {
|
|
@@ -29,6 +29,8 @@ export const makeLivestreamStore = (): StoreApi<LivestreamState> => {
|
|
|
29
29
|
hasReceivedSegment: false,
|
|
30
30
|
moderationPermissions: [],
|
|
31
31
|
setModerationPermissions: (perms) => set({ moderationPermissions: perms }),
|
|
32
|
+
localLivestreamURI: null,
|
|
33
|
+
setLocalLivestreamURI: (uri) => set({ localLivestreamURI: uri }),
|
|
32
34
|
}));
|
|
33
35
|
};
|
|
34
36
|
|
|
@@ -62,7 +64,13 @@ export const useProfile = () => useLivestreamStore((x) => x.profile);
|
|
|
62
64
|
|
|
63
65
|
export const useViewers = () => useLivestreamStore((x) => x.viewers);
|
|
64
66
|
|
|
65
|
-
export const useLivestream = (
|
|
67
|
+
export const useLivestream = (includeEnded: boolean = false) =>
|
|
68
|
+
useLivestreamStore((x) => {
|
|
69
|
+
const ls = x.livestream;
|
|
70
|
+
if (!ls) return null;
|
|
71
|
+
if (!includeEnded && ls.record.endedAt !== undefined) return null;
|
|
72
|
+
return ls;
|
|
73
|
+
});
|
|
66
74
|
|
|
67
75
|
export const useSegment = () => useLivestreamStore((x) => x.segment);
|
|
68
76
|
|
|
@@ -80,6 +80,7 @@ export const handleWebSocketMessages = (
|
|
|
80
80
|
chatProfile: (message as any).chatProfile,
|
|
81
81
|
replyTo: (message as any).replyTo,
|
|
82
82
|
deleted: message.deleted,
|
|
83
|
+
badges: message.badges,
|
|
83
84
|
};
|
|
84
85
|
state = reduceChat(state, [hydrated], [], []);
|
|
85
86
|
} else if (PlaceStreamSegment.isRecord(message)) {
|
|
@@ -32,18 +32,12 @@ export interface PlayerState {
|
|
|
32
32
|
protocol: PlayerProtocol;
|
|
33
33
|
setProtocol: (protocol: PlayerProtocol) => void;
|
|
34
34
|
|
|
35
|
-
/** Source */
|
|
35
|
+
/** Source (streamer did) */
|
|
36
36
|
src: string;
|
|
37
37
|
|
|
38
38
|
/** Function to set the source URL */
|
|
39
39
|
setSrc: (src: string) => void;
|
|
40
40
|
|
|
41
|
-
/** Flag indicating if ingest (stream input) is currently starting */
|
|
42
|
-
ingestStarting: boolean;
|
|
43
|
-
|
|
44
|
-
/** Function to set the ingestStarting flag */
|
|
45
|
-
setIngestStarting: (ingestStarting: boolean) => void;
|
|
46
|
-
|
|
47
41
|
/** Flag indicating if ingest is live */
|
|
48
42
|
ingestLive: boolean;
|
|
49
43
|
setIngestLive: (ingestLive: boolean) => void;
|
|
@@ -20,7 +20,18 @@ export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
|
|
|
20
20
|
id: id || Math.random().toString(36).slice(8),
|
|
21
21
|
selectedRendition: "source",
|
|
22
22
|
setSelectedRendition: (rendition: string) =>
|
|
23
|
-
set((state) =>
|
|
23
|
+
set((state) => {
|
|
24
|
+
if (rendition === "audio" && state.controlsTimeout) {
|
|
25
|
+
clearTimeout(state.controlsTimeout);
|
|
26
|
+
return {
|
|
27
|
+
...state,
|
|
28
|
+
selectedRendition: rendition,
|
|
29
|
+
showControls: true,
|
|
30
|
+
controlsTimeout: undefined,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return { ...state, selectedRendition: rendition };
|
|
34
|
+
}),
|
|
24
35
|
protocol: PlayerProtocol.WEBRTC,
|
|
25
36
|
setProtocol: (protocol: PlayerProtocol) =>
|
|
26
37
|
set((state) => ({ ...state, protocol: protocol })),
|
|
@@ -28,10 +39,6 @@ export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
|
|
|
28
39
|
src: "",
|
|
29
40
|
setSrc: (src: string) => set(() => ({ src })),
|
|
30
41
|
|
|
31
|
-
ingestStarting: false,
|
|
32
|
-
setIngestStarting: (ingestStarting: boolean) =>
|
|
33
|
-
set(() => ({ ingestStarting })),
|
|
34
|
-
|
|
35
42
|
ingestMediaSource: undefined,
|
|
36
43
|
setIngestMediaSource: (ingestMediaSource: IngestMediaSource | undefined) =>
|
|
37
44
|
set(() => ({ ingestMediaSource })),
|
|
@@ -45,7 +52,7 @@ export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
|
|
|
45
52
|
ingestConnectionState: RTCPeerConnectionState | null,
|
|
46
53
|
) => set(() => ({ ingestConnectionState })),
|
|
47
54
|
|
|
48
|
-
ingestAutoStart:
|
|
55
|
+
ingestAutoStart: true,
|
|
49
56
|
setIngestAutoStart: (ingestAutoStart: boolean) =>
|
|
50
57
|
set(() => ({ ingestAutoStart })),
|
|
51
58
|
|
|
@@ -171,10 +178,12 @@ export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
|
|
|
171
178
|
|
|
172
179
|
setUserInteraction: () =>
|
|
173
180
|
set((p) => {
|
|
174
|
-
// controls timeout
|
|
175
181
|
if (p.controlsTimeout) {
|
|
176
182
|
clearTimeout(p.controlsTimeout);
|
|
177
183
|
}
|
|
184
|
+
if (p.selectedRendition === "audio") {
|
|
185
|
+
return { showControls: true, controlsTimeout: undefined };
|
|
186
|
+
}
|
|
178
187
|
let controlsTimeout = setTimeout(() => p.setShowControls(false), 1000);
|
|
179
188
|
return { showControls: true, controlsTimeout };
|
|
180
189
|
}),
|
|
@@ -143,16 +143,12 @@ export function useCurrentPlayerRendition(): [
|
|
|
143
143
|
* Hook to get the ingest state of the current player
|
|
144
144
|
*/
|
|
145
145
|
export function useCurrentPlayerIngest(): {
|
|
146
|
-
starting: boolean;
|
|
147
|
-
setStarting: (starting: boolean) => void;
|
|
148
146
|
connectionState: RTCPeerConnectionState | null;
|
|
149
147
|
setConnectionState: (state: RTCPeerConnectionState | null) => void;
|
|
150
148
|
startedTimestamp: number | null;
|
|
151
149
|
setStartedTimestamp: (timestamp: number | null) => void;
|
|
152
150
|
} {
|
|
153
151
|
return useCurrentPlayerStore((state) => ({
|
|
154
|
-
starting: state.ingestStarting,
|
|
155
|
-
setStarting: state.setIngestStarting,
|
|
156
152
|
connectionState: state.ingestConnectionState,
|
|
157
153
|
setConnectionState: state.setIngestConnectionState,
|
|
158
154
|
startedTimestamp: state.ingestStarted,
|
|
@@ -127,111 +127,25 @@ export function useCreateStreamRecord() {
|
|
|
127
127
|
let agent = usePDSAgent();
|
|
128
128
|
let url = useUrl();
|
|
129
129
|
const uploadThumbnail = useUploadThumbnail();
|
|
130
|
-
|
|
131
130
|
return async ({
|
|
132
131
|
title,
|
|
133
132
|
customThumbnail,
|
|
134
133
|
submitPost,
|
|
135
134
|
canonicalUrl,
|
|
136
135
|
notificationSettings,
|
|
136
|
+
idleTimeoutSeconds,
|
|
137
137
|
}: {
|
|
138
138
|
title: string;
|
|
139
139
|
customThumbnail?: Blob;
|
|
140
140
|
submitPost?: boolean;
|
|
141
141
|
canonicalUrl?: string;
|
|
142
142
|
notificationSettings?: PlaceStreamLivestream.NotificationSettings;
|
|
143
|
+
idleTimeoutSeconds?: number;
|
|
143
144
|
}) => {
|
|
144
|
-
if (typeof submitPost !== "boolean") {
|
|
145
|
-
submitPost = true;
|
|
146
|
-
}
|
|
147
145
|
if (!agent) {
|
|
148
146
|
throw new Error("No PDS agent found");
|
|
149
147
|
}
|
|
150
148
|
|
|
151
|
-
if (!agent.did) {
|
|
152
|
-
throw new Error("No user DID found, assuming not logged in");
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const u = new URL(url);
|
|
156
|
-
|
|
157
|
-
let thumbnail: BlobRef | undefined = undefined;
|
|
158
|
-
|
|
159
|
-
if (customThumbnail) {
|
|
160
|
-
try {
|
|
161
|
-
thumbnail = await uploadThumbnail(agent, customThumbnail);
|
|
162
|
-
} catch (e) {
|
|
163
|
-
throw new Error(`Custom thumbnail upload failed ${e}`);
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
// No custom thumbnail: fetch the server-side image and upload it
|
|
167
|
-
// try thrice lel
|
|
168
|
-
let tries = 0;
|
|
169
|
-
try {
|
|
170
|
-
for (; tries < 3; tries++) {
|
|
171
|
-
try {
|
|
172
|
-
console.log(
|
|
173
|
-
`Fetching thumbnail from ${u.protocol}//${u.host}/api/playback/${agent.did}/stream.png`,
|
|
174
|
-
);
|
|
175
|
-
const thumbnailRes = await fetch(
|
|
176
|
-
`${u.protocol}//${u.host}/api/playback/${agent.did}/stream.png`,
|
|
177
|
-
);
|
|
178
|
-
if (!thumbnailRes.ok) {
|
|
179
|
-
throw new Error(
|
|
180
|
-
`Failed to fetch thumbnail: ${thumbnailRes.status})`,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
const thumbnailBlob = await thumbnailRes.blob();
|
|
184
|
-
console.log(thumbnailBlob);
|
|
185
|
-
thumbnail = await uploadThumbnail(agent, thumbnailBlob);
|
|
186
|
-
} catch (e) {
|
|
187
|
-
console.warn(
|
|
188
|
-
`Failed to fetch thumbnail, retrying (${tries + 1}/3): ${e}`,
|
|
189
|
-
);
|
|
190
|
-
// Wait 1 second before retrying
|
|
191
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
192
|
-
if (tries === 2) {
|
|
193
|
-
throw new Error(`Failed to fetch thumbnail after 3 tries: ${e}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
} catch (e) {
|
|
198
|
-
throw new Error(`Thumbnail upload failed ${e}`);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
let newPost: undefined | { uri: string; cid: string } = undefined;
|
|
203
|
-
|
|
204
|
-
const did = agent.did;
|
|
205
|
-
const profile = await agent.getProfile({ actor: did });
|
|
206
|
-
|
|
207
|
-
if (submitPost) {
|
|
208
|
-
if (!profile) {
|
|
209
|
-
throw new Error("No profile found for the user DID");
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const params = new URLSearchParams({
|
|
213
|
-
did: did,
|
|
214
|
-
time: new Date().toISOString(),
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
let post = await buildGoLivePost(
|
|
218
|
-
title,
|
|
219
|
-
u,
|
|
220
|
-
profile.data,
|
|
221
|
-
params,
|
|
222
|
-
thumbnail,
|
|
223
|
-
agent,
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
newPost = await createNewPost(agent, post);
|
|
227
|
-
|
|
228
|
-
if (!newPost.uri || !newPost.cid) {
|
|
229
|
-
throw new Error(
|
|
230
|
-
"Cannot read properties of undefined (reading 'uri' or 'cid')",
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
149
|
let platform: string = Platform.OS;
|
|
236
150
|
let platVersion: string = Platform.Version
|
|
237
151
|
? Platform.Version.toString()
|
|
@@ -244,36 +158,50 @@ export function useCreateStreamRecord() {
|
|
|
244
158
|
) {
|
|
245
159
|
platVersion = getBrowserName(window.navigator.userAgent);
|
|
246
160
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (!canonicalUrl) {
|
|
250
|
-
canonicalUrl = thisUrl;
|
|
161
|
+
if (!agent.did) {
|
|
162
|
+
throw new Error("No user DID found, assuming not logged in");
|
|
251
163
|
}
|
|
252
164
|
|
|
165
|
+
const thisUrl = `${url}/${agent.did}`;
|
|
166
|
+
|
|
253
167
|
const record: PlaceStreamLivestream.Record = {
|
|
254
168
|
$type: "place.stream.livestream",
|
|
255
169
|
title: title,
|
|
256
170
|
url: thisUrl,
|
|
257
171
|
createdAt: new Date().toISOString(),
|
|
172
|
+
lastSeenAt: new Date().toISOString(),
|
|
258
173
|
// would match up with e.g. https://stream.place/iame.li
|
|
259
174
|
canonicalUrl: canonicalUrl,
|
|
260
175
|
// user agent style string
|
|
261
176
|
// e.g. `@streamplace/components/0.1.0 (ios, 32.0)`
|
|
262
177
|
agent: `@streamplace/components/${PackageJson.version} (${platform}, ${platVersion})`,
|
|
263
|
-
|
|
264
|
-
thumb: thumbnail,
|
|
178
|
+
idleTimeoutSeconds: idleTimeoutSeconds,
|
|
265
179
|
};
|
|
266
180
|
|
|
267
181
|
if (notificationSettings) {
|
|
268
182
|
record.notificationSettings = notificationSettings;
|
|
269
183
|
}
|
|
270
184
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
185
|
+
if (customThumbnail) {
|
|
186
|
+
try {
|
|
187
|
+
const thumbnail = await uploadThumbnail(agent, customThumbnail);
|
|
188
|
+
record.thumb = thumbnail;
|
|
189
|
+
} catch (e) {
|
|
190
|
+
throw new Error(`Custom thumbnail upload failed ${e}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const output = await agent.place.stream.live.startLivestream({
|
|
195
|
+
livestream: record,
|
|
196
|
+
streamer: agent.did,
|
|
197
|
+
createBlueskyPost: submitPost,
|
|
275
198
|
});
|
|
276
|
-
|
|
199
|
+
|
|
200
|
+
if (!output.success) {
|
|
201
|
+
throw new Error("Failed to start livestream");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return output.data;
|
|
277
205
|
};
|
|
278
206
|
}
|
|
279
207
|
|
|
@@ -339,3 +267,18 @@ export function useUpdateStreamRecord(customUrl: string | null = null) {
|
|
|
339
267
|
return record;
|
|
340
268
|
};
|
|
341
269
|
}
|
|
270
|
+
|
|
271
|
+
export function useEndLivestream() {
|
|
272
|
+
let agent = usePDSAgent();
|
|
273
|
+
return async () => {
|
|
274
|
+
if (!agent) {
|
|
275
|
+
throw new Error("No PDS agent found");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!agent.did) {
|
|
279
|
+
throw new Error("No user DID found, assuming not logged in");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return await agent.place.stream.live.stopLivestream({});
|
|
283
|
+
};
|
|
284
|
+
}
|
|
Binary file
|