mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Live player fixes and improvements (#11855)
* Only set stalled error when player is visible * Show activity indicator before live player starts playing * remove comment * keep gradients when still image is showing * fix chips * red dot and outline
This commit is contained in:
		
							parent
							
								
									30b86271ea
								
							
						
					
					
						commit
						18d561da0e
					
				| @ -10,6 +10,7 @@ type JSMpegPlayerProps = { | |||||||
|   width: number; |   width: number; | ||||||
|   height: number; |   height: number; | ||||||
|   containerRef?: React.MutableRefObject<HTMLDivElement | null>; |   containerRef?: React.MutableRefObject<HTMLDivElement | null>; | ||||||
|  |   onPlaying?: () => void; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default function JSMpegPlayer({ | export default function JSMpegPlayer({ | ||||||
| @ -18,6 +19,7 @@ export default function JSMpegPlayer({ | |||||||
|   height, |   height, | ||||||
|   className, |   className, | ||||||
|   containerRef, |   containerRef, | ||||||
|  |   onPlaying, | ||||||
| }: JSMpegPlayerProps) { | }: JSMpegPlayerProps) { | ||||||
|   const url = `${baseUrl.replace(/^http/, "ws")}live/jsmpeg/${camera}`; |   const url = `${baseUrl.replace(/^http/, "ws")}live/jsmpeg/${camera}`; | ||||||
|   const playerRef = useRef<HTMLDivElement | null>(null); |   const playerRef = useRef<HTMLDivElement | null>(null); | ||||||
| @ -88,7 +90,14 @@ export default function JSMpegPlayer({ | |||||||
|       playerRef.current, |       playerRef.current, | ||||||
|       url, |       url, | ||||||
|       { canvas: `#${CSS.escape(uniqueId)}` }, |       { canvas: `#${CSS.escape(uniqueId)}` }, | ||||||
|       { protocols: [], audio: false, videoBufferSize: 1024 * 1024 * 4 }, |       { | ||||||
|  |         protocols: [], | ||||||
|  |         audio: false, | ||||||
|  |         videoBufferSize: 1024 * 1024 * 4, | ||||||
|  |         onPlay: () => { | ||||||
|  |           onPlaying?.(); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     return () => { |     return () => { | ||||||
| @ -100,7 +109,7 @@ export default function JSMpegPlayer({ | |||||||
|         playerRef.current = null; |         playerRef.current = null; | ||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
|   }, [url, uniqueId]); |   }, [url, uniqueId, onPlaying]); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className={className}> |     <div className={className}> | ||||||
|  | |||||||
| @ -172,6 +172,7 @@ export default function LivePlayer({ | |||||||
|           width={cameraConfig.detect.width} |           width={cameraConfig.detect.width} | ||||||
|           height={cameraConfig.detect.height} |           height={cameraConfig.detect.height} | ||||||
|           containerRef={containerRef} |           containerRef={containerRef} | ||||||
|  |           onPlaying={() => setLiveReady(true)} | ||||||
|         /> |         /> | ||||||
|       ); |       ); | ||||||
|     } else { |     } else { | ||||||
| @ -187,7 +188,8 @@ export default function LivePlayer({ | |||||||
|       data-camera={cameraConfig.name} |       data-camera={cameraConfig.name} | ||||||
|       className={cn( |       className={cn( | ||||||
|         "relative flex w-full cursor-pointer justify-center outline", |         "relative flex w-full cursor-pointer justify-center outline", | ||||||
|         activeTracking |         activeTracking && | ||||||
|  |           ((showStillWithoutActivity && !liveReady) || liveReady) | ||||||
|           ? "outline-3 rounded-lg shadow-severity_alert outline-severity_alert md:rounded-2xl" |           ? "outline-3 rounded-lg shadow-severity_alert outline-severity_alert md:rounded-2xl" | ||||||
|           : "outline-0 outline-background", |           : "outline-0 outline-background", | ||||||
|         "transition-all duration-500", |         "transition-all duration-500", | ||||||
| @ -195,52 +197,60 @@ export default function LivePlayer({ | |||||||
|       )} |       )} | ||||||
|       onClick={onClick} |       onClick={onClick} | ||||||
|     > |     > | ||||||
|       <div className="pointer-events-none absolute inset-x-0 top-0 z-10 h-[30%] w-full rounded-lg bg-gradient-to-b from-black/20 to-transparent md:rounded-2xl"></div> |       {((showStillWithoutActivity && !liveReady) || liveReady) && ( | ||||||
|       <div className="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-[10%] w-full rounded-lg bg-gradient-to-t from-black/20 to-transparent md:rounded-2xl"></div> |         <> | ||||||
|       {player} |           <div className="pointer-events-none absolute inset-x-0 top-0 z-10 h-[30%] w-full rounded-lg bg-gradient-to-b from-black/20 to-transparent md:rounded-2xl"></div> | ||||||
| 
 |           <div className="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-[10%] w-full rounded-lg bg-gradient-to-t from-black/20 to-transparent md:rounded-2xl"></div> | ||||||
|       {objects.length > 0 && ( |         </> | ||||||
|         <div className="absolute left-0 top-2 z-40"> |  | ||||||
|           <Tooltip> |  | ||||||
|             <div className="flex"> |  | ||||||
|               <TooltipTrigger asChild> |  | ||||||
|                 <div className="mx-3 pb-1 text-sm text-white"> |  | ||||||
|                   <Chip |  | ||||||
|                     className={`z-0 flex items-start justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500`} |  | ||||||
|                   > |  | ||||||
|                     {[ |  | ||||||
|                       ...new Set([ |  | ||||||
|                         ...(objects || []).map(({ label }) => label), |  | ||||||
|                       ]), |  | ||||||
|                     ] |  | ||||||
|                       .map((label) => { |  | ||||||
|                         return getIconForLabel(label, "size-3 text-white"); |  | ||||||
|                       }) |  | ||||||
|                       .sort()} |  | ||||||
|                   </Chip> |  | ||||||
|                 </div> |  | ||||||
|               </TooltipTrigger> |  | ||||||
|             </div> |  | ||||||
|             <TooltipContent className="capitalize"> |  | ||||||
|               {[ |  | ||||||
|                 ...new Set([ |  | ||||||
|                   ...(objects || []).map(({ label, sub_label }) => |  | ||||||
|                     label.endsWith("verified") ? sub_label : label, |  | ||||||
|                   ), |  | ||||||
|                 ]), |  | ||||||
|               ] |  | ||||||
|                 .filter( |  | ||||||
|                   (label) => |  | ||||||
|                     label !== undefined && !label.includes("-verified"), |  | ||||||
|                 ) |  | ||||||
|                 .map((label) => capitalizeFirstLetter(label)) |  | ||||||
|                 .sort() |  | ||||||
|                 .join(", ") |  | ||||||
|                 .replaceAll("-verified", "")} |  | ||||||
|             </TooltipContent> |  | ||||||
|           </Tooltip> |  | ||||||
|         </div> |  | ||||||
|       )} |       )} | ||||||
|  |       {player} | ||||||
|  |       {!offline && !showStillWithoutActivity && !liveReady && ( | ||||||
|  |         <ActivityIndicator /> | ||||||
|  |       )} | ||||||
|  | 
 | ||||||
|  |       {((showStillWithoutActivity && !liveReady) || liveReady) && | ||||||
|  |         objects.length > 0 && ( | ||||||
|  |           <div className="absolute left-0 top-2 z-40"> | ||||||
|  |             <Tooltip> | ||||||
|  |               <div className="flex"> | ||||||
|  |                 <TooltipTrigger asChild> | ||||||
|  |                   <div className="mx-3 pb-1 text-sm text-white"> | ||||||
|  |                     <Chip | ||||||
|  |                       className={`z-0 flex items-start justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500`} | ||||||
|  |                     > | ||||||
|  |                       {[ | ||||||
|  |                         ...new Set([ | ||||||
|  |                           ...(objects || []).map(({ label }) => label), | ||||||
|  |                         ]), | ||||||
|  |                       ] | ||||||
|  |                         .map((label) => { | ||||||
|  |                           return getIconForLabel(label, "size-3 text-white"); | ||||||
|  |                         }) | ||||||
|  |                         .sort()} | ||||||
|  |                     </Chip> | ||||||
|  |                   </div> | ||||||
|  |                 </TooltipTrigger> | ||||||
|  |               </div> | ||||||
|  |               <TooltipContent className="capitalize"> | ||||||
|  |                 {[ | ||||||
|  |                   ...new Set([ | ||||||
|  |                     ...(objects || []).map(({ label, sub_label }) => | ||||||
|  |                       label.endsWith("verified") ? sub_label : label, | ||||||
|  |                     ), | ||||||
|  |                   ]), | ||||||
|  |                 ] | ||||||
|  |                   .filter( | ||||||
|  |                     (label) => | ||||||
|  |                       label !== undefined && !label.includes("-verified"), | ||||||
|  |                   ) | ||||||
|  |                   .map((label) => capitalizeFirstLetter(label)) | ||||||
|  |                   .sort() | ||||||
|  |                   .join(", ") | ||||||
|  |                   .replaceAll("-verified", "")} | ||||||
|  |               </TooltipContent> | ||||||
|  |             </Tooltip> | ||||||
|  |           </div> | ||||||
|  |         )} | ||||||
| 
 | 
 | ||||||
|       <div |       <div | ||||||
|         className={`absolute inset-0 w-full ${ |         className={`absolute inset-0 w-full ${ | ||||||
| @ -257,9 +267,12 @@ export default function LivePlayer({ | |||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <div className="absolute right-2 top-2"> |       <div className="absolute right-2 top-2"> | ||||||
|         {autoLive && !offline && activeMotion && ( |         {autoLive && | ||||||
|           <MdCircle className="mr-2 size-2 animate-pulse text-danger shadow-danger drop-shadow-md" /> |           !offline && | ||||||
|         )} |           activeMotion && | ||||||
|  |           ((showStillWithoutActivity && !liveReady) || liveReady) && ( | ||||||
|  |             <MdCircle className="mr-2 size-2 animate-pulse text-danger shadow-danger drop-shadow-md" /> | ||||||
|  |           )} | ||||||
|         {offline && ( |         {offline && ( | ||||||
|           <Chip |           <Chip | ||||||
|             className={`z-0 flex items-start justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs capitalize`} |             className={`z-0 flex items-start justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs capitalize`} | ||||||
|  | |||||||
| @ -328,7 +328,9 @@ function MSEPlayer({ | |||||||
| 
 | 
 | ||||||
|           setBufferTimeout( |           setBufferTimeout( | ||||||
|             setTimeout(() => { |             setTimeout(() => { | ||||||
|               onError("stalled"); |               if (document.visibilityState === "visible") { | ||||||
|  |                 onError("stalled"); | ||||||
|  |               } | ||||||
|             }, 3000), |             }, 3000), | ||||||
|           ); |           ); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -238,7 +238,9 @@ export default function WebRtcPlayer({ | |||||||
| 
 | 
 | ||||||
|               setBufferTimeout( |               setBufferTimeout( | ||||||
|                 setTimeout(() => { |                 setTimeout(() => { | ||||||
|                   onError("stalled"); |                   if (document.visibilityState === "visible") { | ||||||
|  |                     onError("stalled"); | ||||||
|  |                   } | ||||||
|                 }, 3000), |                 }, 3000), | ||||||
|               ); |               ); | ||||||
|             } |             } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user