pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add custom headers to dev video test view

Pas dd2422f8 f1065e96

+146 -4
+146 -4
src/pages/developer/VideoTesterView.tsx
··· 1 - import { useCallback, useState } from "react"; 1 + import { useCallback, useEffect, useState } from "react"; 2 2 3 + import { prepareStream } from "@/backend/extension/streams"; 3 4 import { Button } from "@/components/buttons/Button"; 5 + import { Toggle } from "@/components/buttons/Toggle"; 4 6 import { Dropdown } from "@/components/form/Dropdown"; 7 + import { Icon, Icons } from "@/components/Icon"; 5 8 import { usePlayer } from "@/components/player/hooks/usePlayer"; 6 9 import { Title } from "@/components/text/Title"; 10 + import { AuthInputBox } from "@/components/text-inputs/AuthInputBox"; 7 11 import { TextInputControl } from "@/components/text-inputs/TextInputControl"; 12 + import { Divider } from "@/components/utils/Divider"; 8 13 import { PlaybackErrorPart } from "@/pages/parts/player/PlaybackErrorPart"; 9 14 import { PlayerPart } from "@/pages/parts/player/PlayerPart"; 10 15 import { PlayerMeta, playerStatus } from "@/stores/player/slices/source"; 11 16 import { SourceSliceSource, StreamType } from "@/stores/player/utils/qualities"; 17 + import { type ExtensionStatus, getExtensionState } from "@/utils/extension"; 12 18 13 19 const testMeta: PlayerMeta = { 14 20 releaseYear: 2010, ··· 32 38 const { status, playMedia, setMeta } = usePlayer(); 33 39 const [selected, setSelected] = useState("mp4"); 34 40 const [inputSource, setInputSource] = useState(""); 41 + const [extensionState, setExtensionState] = 42 + useState<ExtensionStatus>("unknown"); 43 + const [headersEnabled, setHeadersEnabled] = useState(false); 44 + const [headers, setHeaders] = useState<Array<{ key: string; value: string }>>( 45 + [{ key: "", value: "" }], 46 + ); 47 + 48 + // Check extension state on mount 49 + useEffect(() => { 50 + getExtensionState().then(setExtensionState); 51 + }, []); 52 + 53 + // Header management functions 54 + const addHeader = useCallback(() => { 55 + setHeaders((prev) => [...prev, { key: "", value: "" }]); 56 + }, []); 57 + 58 + const updateHeader = useCallback( 59 + (index: number, field: "key" | "value", value: string) => { 60 + setHeaders((prev) => 61 + prev.map((header, i) => 62 + i === index ? { ...header, [field]: value } : header, 63 + ), 64 + ); 65 + }, 66 + [], 67 + ); 68 + 69 + const removeHeader = useCallback((index: number) => { 70 + setHeaders((prev) => prev.filter((_, i) => i !== index)); 71 + }, []); 72 + 73 + const toggleHeaders = useCallback(() => { 74 + const newEnabled = !headersEnabled; 75 + setHeadersEnabled(newEnabled); 76 + if (!newEnabled) { 77 + setHeaders([{ key: "", value: "" }]); 78 + } 79 + }, [headersEnabled]); 35 80 36 81 const start = useCallback( 37 - (url: string, type: StreamType) => { 82 + async (url: string, type: StreamType) => { 83 + // Build headers object from enabled headers 84 + const headersObj: Record<string, string> = {}; 85 + if (headersEnabled) { 86 + headers.forEach(({ key, value }) => { 87 + if (key.trim() && value.trim()) { 88 + headersObj[key.trim()] = value.trim(); 89 + } 90 + }); 91 + } 92 + 38 93 let source: SourceSliceSource; 39 94 if (type === "hls") { 40 95 source = { 41 96 type: "hls", 42 97 url, 98 + ...(Object.keys(headersObj).length > 0 && { headers: headersObj }), 43 99 }; 44 100 } else if (type === "mp4") { 45 101 source = { ··· 50 106 url, 51 107 }, 52 108 }, 109 + ...(Object.keys(headersObj).length > 0 && { headers: headersObj }), 53 110 }; 54 111 } else throw new Error("Invalid type"); 112 + 113 + // Prepare stream headers if extension is active and headers are present 114 + if (extensionState === "success" && Object.keys(headersObj).length > 0) { 115 + // Create a mock Stream object for prepareStream 116 + const mockStream: any = { 117 + type: type === "hls" ? "hls" : "file", 118 + ...(type === "hls" 119 + ? { playlist: url } 120 + : { 121 + qualities: { 122 + unknown: { 123 + type: "mp4", 124 + url, 125 + }, 126 + }, 127 + }), 128 + headers: headersObj, 129 + }; 130 + try { 131 + await prepareStream(mockStream); 132 + } catch (error) { 133 + console.warn("Failed to prepare stream headers:", error); 134 + } 135 + } 136 + 55 137 setMeta(testMeta); 56 138 playMedia(source, [], null); 57 139 }, 58 - [playMedia, setMeta], 140 + [playMedia, setMeta, headersEnabled, headers, extensionState], 59 141 ); 60 142 61 143 return ( ··· 85 167 setSelectedItem={(item) => setSelected(item.id)} 86 168 /> 87 169 </div> 170 + 171 + {extensionState === "success" && ( 172 + <div className="flex-1 mb-4"> 173 + <div className="flex justify-between items-center gap-4"> 174 + <div className="my-3"> 175 + <p className="text-white font-bold">Headers</p> 176 + </div> 177 + <div> 178 + <Toggle 179 + onClick={toggleHeaders} 180 + enabled={headersEnabled} 181 + /> 182 + </div> 183 + </div> 184 + {headersEnabled && ( 185 + <> 186 + <Divider marginClass="my-6 px-8 box-content -mx-8" /> 187 + <div className="my-6 space-y-2"> 188 + {headers.length === 0 ? ( 189 + <p>No headers configured.</p> 190 + ) : ( 191 + headers.map((header, index) => ( 192 + <div 193 + // eslint-disable-next-line react/no-array-index-key 194 + key={index} 195 + className="grid grid-cols-[1fr,1fr,auto] items-center gap-2" 196 + > 197 + <AuthInputBox 198 + value={header.key} 199 + onChange={(value) => 200 + updateHeader(index, "key", value) 201 + } 202 + placeholder="Key" 203 + /> 204 + <AuthInputBox 205 + value={header.value} 206 + onChange={(value) => 207 + updateHeader(index, "value", value) 208 + } 209 + placeholder="Value" 210 + /> 211 + <button 212 + type="button" 213 + onClick={() => removeHeader(index)} 214 + className="h-full scale-90 hover:scale-100 rounded-full aspect-square bg-authentication-inputBg hover:bg-authentication-inputBgHover flex justify-center items-center transition-transform duration-200 hover:text-white cursor-pointer" 215 + > 216 + <Icon className="text-xl" icon={Icons.X} /> 217 + </button> 218 + </div> 219 + )) 220 + )} 221 + </div> 222 + 223 + <Button theme="purple" onClick={addHeader}> 224 + Add header 225 + </Button> 226 + </> 227 + )} 228 + </div> 229 + )} 230 + 88 231 <Button 89 232 onClick={() => start(inputSource, selected as StreamType)} 90 233 > 91 234 Start stream 92 235 </Button> 93 236 </div> 94 - 95 237 <div className="flex-1"> 96 238 <Title>Preset tests</Title> 97 239 <div className="grid grid-cols-[1fr,1fr] gap-2">