Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Bw-bestperf
SAM2
Commits
17d316f3
Commit
17d316f3
authored
Feb 04, 2026
by
suily
Browse files
Initial commit
parents
Pipeline
#3368
failed with stages
in 0 seconds
Changes
959
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
1426 additions
and
0 deletions
+1426
-0
demo/frontend/src/common/components/snackbar/useDemoMessagesSnackbar.ts
...src/common/components/snackbar/useDemoMessagesSnackbar.ts
+46
-0
demo/frontend/src/common/components/snackbar/useExpireMessage.ts
...ontend/src/common/components/snackbar/useExpireMessage.ts
+65
-0
demo/frontend/src/common/components/snackbar/useMessagesSnackbar.ts
...end/src/common/components/snackbar/useMessagesSnackbar.ts
+60
-0
demo/frontend/src/common/components/toolbar/DesktopToolbar.tsx
...frontend/src/common/components/toolbar/DesktopToolbar.tsx
+45
-0
demo/frontend/src/common/components/toolbar/MobileToolbar.tsx
.../frontend/src/common/components/toolbar/MobileToolbar.tsx
+35
-0
demo/frontend/src/common/components/toolbar/Toolbar.tsx
demo/frontend/src/common/components/toolbar/Toolbar.tsx
+94
-0
demo/frontend/src/common/components/toolbar/ToolbarActionIcon.tsx
...ntend/src/common/components/toolbar/ToolbarActionIcon.tsx
+99
-0
demo/frontend/src/common/components/toolbar/ToolbarBottomActionsWrapper.tsx
...common/components/toolbar/ToolbarBottomActionsWrapper.tsx
+38
-0
demo/frontend/src/common/components/toolbar/ToolbarConfig.tsx
.../frontend/src/common/components/toolbar/ToolbarConfig.tsx
+18
-0
demo/frontend/src/common/components/toolbar/ToolbarHeaderWrapper.tsx
...nd/src/common/components/toolbar/ToolbarHeaderWrapper.tsx
+48
-0
demo/frontend/src/common/components/toolbar/ToolbarProgressChip.tsx
...end/src/common/components/toolbar/ToolbarProgressChip.tsx
+49
-0
demo/frontend/src/common/components/toolbar/ToolbarSection.tsx
...frontend/src/common/components/toolbar/ToolbarSection.tsx
+34
-0
demo/frontend/src/common/components/toolbar/useListenToStreamingState.ts
...rc/common/components/toolbar/useListenToStreamingState.ts
+56
-0
demo/frontend/src/common/components/toolbar/useToolbarTabs.ts
.../frontend/src/common/components/toolbar/useToolbarTabs.ts
+23
-0
demo/frontend/src/common/components/useFunctionThrottle.tsx
demo/frontend/src/common/components/useFunctionThrottle.tsx
+92
-0
demo/frontend/src/common/components/video/ChangeVideoModal.tsx
...frontend/src/common/components/video/ChangeVideoModal.tsx
+83
-0
demo/frontend/src/common/components/video/EventEmitter.ts
demo/frontend/src/common/components/video/EventEmitter.ts
+56
-0
demo/frontend/src/common/components/video/Video.tsx
demo/frontend/src/common/components/video/Video.tsx
+374
-0
demo/frontend/src/common/components/video/VideoFilmstripWithPlayback.tsx
...rc/common/components/video/VideoFilmstripWithPlayback.tsx
+52
-0
demo/frontend/src/common/components/video/VideoLoadingOverlay.tsx
...ntend/src/common/components/video/VideoLoadingOverlay.tsx
+59
-0
No files found.
demo/frontend/src/common/components/snackbar/useDemoMessagesSnackbar.ts
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
MessagesEventMap
}
from
'
@/common/components/snackbar/DemoMessagesSnackbarUtils
'
;
import
useMessagesSnackbar
from
'
@/common/components/snackbar/useMessagesSnackbar
'
;
import
{
messageMapAtom
}
from
'
@/demo/atoms
'
;
import
{
useAtom
}
from
'
jotai
'
;
import
{
useCallback
}
from
'
react
'
;
type
State
=
{
enqueueMessage
:
(
messageType
:
keyof
MessagesEventMap
)
=>
void
;
clearMessage
:
()
=>
void
;
};
export
default
function
useDemoMessagesSnackbar
():
State
{
const
[
messageMap
,
setMessageMap
]
=
useAtom
(
messageMapAtom
);
const
{
enqueueMessage
:
enqueue
,
clearMessage
}
=
useMessagesSnackbar
();
const
enqueueMessage
=
useCallback
(
(
messageType
:
keyof
MessagesEventMap
)
=>
{
const
{
text
,
shown
,
options
}
=
messageMap
[
messageType
];
if
(
!
options
?.
repeat
&&
shown
===
true
)
{
return
;
}
enqueue
(
text
,
options
);
const
newState
=
{...
messageMap
};
newState
[
messageType
].
shown
=
true
;
setMessageMap
(
newState
);
},
[
enqueue
,
messageMap
,
setMessageMap
],
);
return
{
enqueueMessage
,
clearMessage
};
}
demo/frontend/src/common/components/snackbar/useExpireMessage.ts
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
useAtom
}
from
'
jotai
'
;
import
{
useEffect
,
useRef
}
from
'
react
'
;
import
{
Message
,
messageAtom
}
from
'
@/common/components/snackbar/snackbarAtoms
'
;
export
default
function
useExpireMessage
()
{
const
[
message
,
setMessage
]
=
useAtom
(
messageAtom
);
const
messageRef
=
useRef
<
Message
|
null
>
(
null
);
const
intervalRef
=
useRef
<
NodeJS
.
Timeout
|
null
>
(
null
);
useEffect
(()
=>
{
messageRef
.
current
=
message
;
},
[
message
]);
useEffect
(()
=>
{
function
resetInterval
()
{
if
(
intervalRef
.
current
!=
null
)
{
clearInterval
(
intervalRef
.
current
);
intervalRef
.
current
=
null
;
}
}
if
(
intervalRef
.
current
==
null
&&
message
!=
null
&&
message
.
expire
)
{
intervalRef
.
current
=
setInterval
(()
=>
{
const
prevMessage
=
messageRef
.
current
;
if
(
prevMessage
==
null
)
{
setMessage
(
null
);
resetInterval
();
return
;
}
const
messageDuration
=
Date
.
now
()
-
prevMessage
.
startTime
;
if
(
messageDuration
>
prevMessage
.
duration
)
{
setMessage
(
null
);
resetInterval
();
return
;
}
setMessage
({
...
prevMessage
,
progress
:
messageDuration
/
prevMessage
.
duration
,
});
},
20
);
}
},
[
message
,
setMessage
]);
useEffect
(()
=>
{
return
()
=>
{
if
(
intervalRef
.
current
!=
null
)
{
clearInterval
(
intervalRef
.
current
);
}
};
},
[]);
}
demo/frontend/src/common/components/snackbar/useMessagesSnackbar.ts
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
useSetAtom
}
from
'
jotai
'
;
import
{
useCallback
}
from
'
react
'
;
import
{
MessageType
,
messageAtom
,
}
from
'
@/common/components/snackbar/snackbarAtoms
'
;
export
type
EnqueueOption
=
{
duration
?:
number
;
type
?:
MessageType
;
expire
?:
boolean
;
showClose
?:
boolean
;
showReset
?:
boolean
;
};
type
State
=
{
clearMessage
:
()
=>
void
;
enqueueMessage
:
(
message
:
string
,
options
?:
EnqueueOption
)
=>
void
;
};
export
default
function
useMessagesSnackbar
():
State
{
const
setMessage
=
useSetAtom
(
messageAtom
);
const
enqueueMessage
=
useCallback
(
(
message
:
string
,
options
?:
EnqueueOption
)
=>
{
setMessage
({
text
:
message
,
type
:
options
?.
type
??
'
info
'
,
duration
:
options
?.
duration
??
5000
,
progress
:
0
,
startTime
:
Date
.
now
(),
expire
:
options
?.
expire
??
true
,
showClose
:
options
?.
showClose
??
true
,
showReset
:
options
?.
showReset
??
false
,
});
},
[
setMessage
],
);
function
clearMessage
()
{
setMessage
(
null
);
}
return
{
enqueueMessage
,
clearMessage
};
}
demo/frontend/src/common/components/toolbar/DesktopToolbar.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
ObjectsToolbar
from
'
@/common/components/annotations/ObjectsToolbar
'
;
import
EffectsToolbar
from
'
@/common/components/effects/EffectsToolbar
'
;
import
MoreOptionsToolbar
from
'
@/common/components/options/MoreOptionsToolbar
'
;
import
type
{
CSSProperties
}
from
'
react
'
;
type
Props
=
{
tabIndex
:
number
;
onTabChange
:
(
newIndex
:
number
)
=>
void
;
};
export
default
function
DesktopToolbar
({
tabIndex
,
onTabChange
}:
Props
)
{
const
toolbarShadow
:
CSSProperties
=
{
boxShadow
:
'
0px 1px 3px 1px rgba(0,0,0,.25)
'
,
transition
:
'
box-shadow 0.8s ease-out
'
,
};
const
tabs
=
[
<
ObjectsToolbar
key
=
"objects"
onTabChange
=
{
onTabChange
}
/>,
<
EffectsToolbar
key
=
"effects"
onTabChange
=
{
onTabChange
}
/>,
<
MoreOptionsToolbar
key
=
"options"
onTabChange
=
{
onTabChange
}
/>,
];
return
(
<
div
style
=
{
toolbarShadow
}
className
=
"bg-graydark-800 text-white md:basis-[350px] lg:basis-[435px] shrink-0 rounded-xl"
>
{
tabs
[
tabIndex
]
}
</
div
>
);
}
demo/frontend/src/common/components/toolbar/MobileToolbar.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
MobileObjectsToolbar
from
'
@/common/components/annotations/MobileObjectsToolbar
'
;
import
MobileEffectsToolbar
from
'
@/common/components/effects/MobileEffectsToolbar
'
;
import
MoreOptionsToolbar
from
'
@/common/components/options/MoreOptionsToolbar
'
;
type
Props
=
{
tabIndex
:
number
;
onTabChange
:
(
newIndex
:
number
)
=>
void
;
};
export
default
function
MobileToolbar
({
tabIndex
,
onTabChange
}:
Props
)
{
const
tabs
=
[
<
MobileObjectsToolbar
key
=
"objects"
onTabChange
=
{
onTabChange
}
/>,
<
MobileEffectsToolbar
key
=
"effects"
onTabChange
=
{
onTabChange
}
/>,
<
MoreOptionsToolbar
key
=
"more-options"
onTabChange
=
{
onTabChange
}
/>,
];
return
(
<
div
className
=
"relative flex flex-col bg-black"
>
{
tabs
[
tabIndex
]
}
</
div
>
);
}
demo/frontend/src/common/components/toolbar/Toolbar.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
useListenToStreamingState
from
'
@/common/components/toolbar/useListenToStreamingState
'
;
import
useToolbarTabs
from
'
@/common/components/toolbar/useToolbarTabs
'
;
import
useVideo
from
'
@/common/components/video/editor/useVideo
'
;
import
useVideoEffect
from
'
@/common/components/video/editor/useVideoEffect
'
;
import
{
EffectIndex
}
from
'
@/common/components/video/effects/Effects
'
;
import
useScreenSize
from
'
@/common/screen/useScreenSize
'
;
import
{
codeEditorOpenedAtom
,
isPlayingAtom
,
isStreamingAtom
,
}
from
'
@/demo/atoms
'
;
import
{
useAtom
,
useAtomValue
,
useSetAtom
}
from
'
jotai
'
;
import
{
useCallback
,
useEffect
}
from
'
react
'
;
import
DesktopToolbar
from
'
./DesktopToolbar
'
;
import
MobileToolbar
from
'
./MobileToolbar
'
;
import
{
OBJECT_TOOLBAR_INDEX
}
from
'
./ToolbarConfig
'
;
export
default
function
Toolbar
()
{
const
[
tabIndex
,
setTabIndex
]
=
useToolbarTabs
();
const
video
=
useVideo
();
const
setIsPlaying
=
useSetAtom
(
isPlayingAtom
);
const
[
isStreaming
,
setIsStreaming
]
=
useAtom
(
isStreamingAtom
);
const
codeEditorOpened
=
useAtomValue
(
codeEditorOpenedAtom
);
const
{
isMobile
}
=
useScreenSize
();
const
setEffect
=
useVideoEffect
();
const
resetEffects
=
useCallback
(()
=>
{
setEffect
(
'
Original
'
,
EffectIndex
.
BACKGROUND
,
{
variant
:
0
});
setEffect
(
'
Overlay
'
,
EffectIndex
.
HIGHLIGHT
,
{
variant
:
0
});
},
[
setEffect
]);
const
handleStopVideo
=
useCallback
(()
=>
{
if
(
isStreaming
)
{
video
?.
abortStreamMasks
();
}
else
{
video
?.
pause
();
}
},
[
video
,
isStreaming
]);
const
handleTabChange
=
useCallback
(
(
newIndex
:
number
)
=>
{
if
(
newIndex
===
OBJECT_TOOLBAR_INDEX
)
{
handleStopVideo
();
resetEffects
();
}
setTabIndex
(
newIndex
);
},
[
handleStopVideo
,
resetEffects
,
setTabIndex
],
);
useListenToStreamingState
();
useEffect
(()
=>
{
function
onPlay
()
{
setIsPlaying
(
true
);
}
function
onPause
()
{
setIsPlaying
(
false
);
}
video
?.
addEventListener
(
'
play
'
,
onPlay
);
video
?.
addEventListener
(
'
pause
'
,
onPause
);
return
()
=>
{
video
?.
removeEventListener
(
'
play
'
,
onPlay
);
video
?.
removeEventListener
(
'
pause
'
,
onPause
);
};
},
[
video
,
resetEffects
,
setIsStreaming
,
setIsPlaying
]);
if
(
codeEditorOpened
)
{
return
null
;
}
return
isMobile
?
(
<
MobileToolbar
tabIndex
=
{
tabIndex
}
onTabChange
=
{
handleTabChange
}
/>
)
:
(
<
DesktopToolbar
tabIndex
=
{
tabIndex
}
onTabChange
=
{
handleTabChange
}
/>
);
}
demo/frontend/src/common/components/toolbar/ToolbarActionIcon.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
GradientBorder
from
'
@/common/components/button/GradientBorder
'
;
import
useScreenSize
from
'
@/common/screen/useScreenSize
'
;
import
{
BLUE_PINK_FILL_BR
}
from
'
@/theme/gradientStyle
'
;
import
type
{
CarbonIconType
}
from
'
@carbon/icons-react
'
;
import
{
Loading
}
from
'
react-daisyui
'
;
type
Props
=
{
isDisabled
?:
boolean
;
isActive
?:
boolean
;
icon
:
CarbonIconType
;
title
:
string
;
badge
?:
React
.
ReactNode
;
variant
:
'
toggle
'
|
'
button
'
|
'
gradient
'
|
'
flat
'
;
span
?:
1
|
2
;
loadingProps
?:
{
loading
:
boolean
;
label
?:
string
;
};
onClick
:
()
=>
void
;
};
export
default
function
ToolbarActionIcon
({
variant
,
isDisabled
=
false
,
isActive
=
false
,
title
,
badge
,
loadingProps
,
icon
:
Icon
,
span
=
1
,
onClick
,
}:
Props
)
{
const
{
isMobile
}
=
useScreenSize
();
const
isLoading
=
loadingProps
?.
loading
===
true
;
function
handleClick
()
{
if
(
isDisabled
)
{
return
;
}
onClick
();
}
const
ButtonBase
=
(
<
div
onClick
=
{
handleClick
}
className
=
{
`relative rounded-lg h-full flex items-center justify-center select-none
${
!
isDisabled
&&
'
cursor-pointer hover:bg-black
'
}
${
span
===
1
&&
'
col-span-1
'
}
${
span
===
2
&&
'
col-span-2
'
}
${
variant
===
'
button
'
&&
(
isDisabled
?
'
bg-graydark-500 text-gray-300
'
:
'
bg-graydark-700 hover:bg-graydark-800 text-white
'
)}
${
variant
===
'
toggle
'
&&
(
isActive
?
BLUE_PINK_FILL_BR
:
'
bg-inherit
'
)}
${
variant
===
'
flat
'
&&
(
isDisabled
?
'
text-gray-600
'
:
'
text-white
'
)}
`
}
>
<
div
className
=
"py-4 px-2"
>
<
div
className
=
"flex items-center justify-center"
>
{
isLoading
?
(
<
Loading
size
=
"md"
className
=
"mx-auto"
/>
)
:
(
<
Icon
size
=
{
isMobile
?
24
:
28
}
color
=
{
isActive
?
'
white
'
:
'
black
'
}
className
=
{
`mx-auto
${
isDisabled
?
'
text-gray-300
'
:
'
text-white
'
}
`
}
/>
)
}
</
div
>
<
div
className
=
{
`mt-1 md:mt-2 text-center text-xs font-bold
${
isActive
&&
'
text-white
'
}
`
}
>
{
isLoading
&&
loadingProps
?.
label
!=
null
?
loadingProps
.
label
:
title
}
</
div
>
{
isActive
&&
badge
}
</
div
>
</
div
>
);
return
variant
==
'
gradient
'
?
(
<
GradientBorder
rounded
=
{
false
}
className
=
"rounded-lg h-full text-white"
>
{
ButtonBase
}
</
GradientBorder
>
)
:
(
ButtonBase
);
}
demo/frontend/src/common/components/toolbar/ToolbarBottomActionsWrapper.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
spacing
}
from
'
@/theme/tokens.stylex
'
;
import
stylex
from
'
@stylexjs/stylex
'
;
import
{
PropsWithChildren
}
from
'
react
'
;
const
styles
=
stylex
.
create
({
container
:
{
display
:
'
flex
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
space-between
'
,
paddingTop
:
{
default
:
spacing
[
2
],
'
@media screen and (max-width: 768px)
'
:
spacing
[
4
],
},
paddingBottom
:
spacing
[
6
],
paddingHorizontal
:
spacing
[
6
],
},
});
export
default
function
ToolbarBottomActionsWrapper
({
children
,
}:
PropsWithChildren
)
{
return
<
div
{
...
stylex
.
props
(
styles
.
container
)
}
>
{
children
}
</
div
>;
}
demo/frontend/src/common/components/toolbar/ToolbarConfig.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export
const
OBJECT_TOOLBAR_INDEX
=
0
;
export
const
EFFECT_TOOLBAR_INDEX
=
1
;
export
const
MORE_OPTIONS_TOOLBAR_INDEX
=
2
;
demo/frontend/src/common/components/toolbar/ToolbarHeaderWrapper.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
ReactNode
}
from
'
react
'
;
import
ToolbarProgressChip
from
'
./ToolbarProgressChip
'
;
type
Props
=
{
title
:
string
;
description
?:
string
;
bottomSection
?:
ReactNode
;
showProgressChip
?:
boolean
;
className
?:
string
;
};
export
default
function
ToolbarHeaderWrapper
({
title
,
description
,
bottomSection
,
showProgressChip
=
true
,
className
,
}:
Props
)
{
return
(
<
div
className
=
{
`flex flex-col gap-2 p-8 border-b border-b-black
${
className
}
`
}
>
<
div
className
=
"flex items-center"
>
{
showProgressChip
&&
<
ToolbarProgressChip
/>
}
<
h2
className
=
"text-xl"
>
{
title
}
</
h2
>
</
div
>
{
description
!=
null
&&
(
<
div
className
=
"flex-1 text-gray-400"
>
{
description
}
</
div
>
)
}
{
bottomSection
!=
null
&&
bottomSection
}
</
div
>
);
}
demo/frontend/src/common/components/toolbar/ToolbarProgressChip.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
OBJECT_TOOLBAR_INDEX
}
from
'
@/common/components/toolbar/ToolbarConfig
'
;
import
useToolbarTabs
from
'
@/common/components/toolbar/useToolbarTabs
'
;
import
{
streamingStateAtom
}
from
'
@/demo/atoms
'
;
import
{
useAtomValue
}
from
'
jotai
'
;
import
{
useMemo
}
from
'
react
'
;
import
{
Loading
}
from
'
react-daisyui
'
;
const
TOTAL_DEMO_STEPS
=
3
;
export
default
function
ToolbarProgressChip
()
{
const
[
toolbarIndex
]
=
useToolbarTabs
();
const
streamingState
=
useAtomValue
(
streamingStateAtom
);
const
showLoader
=
useMemo
(()
=>
{
return
streamingState
===
'
partial
'
||
streamingState
===
'
requesting
'
;
},
[
streamingState
]);
function
getStepValue
()
{
if
(
toolbarIndex
===
OBJECT_TOOLBAR_INDEX
)
{
return
streamingState
!==
'
full
'
?
1
:
2
;
}
return
3
;
}
return
(
<
span
className
=
"inline-flex items-center justify-center rounded-full text-xs md:text-sm font-medium bg-white text-black w-10 md:w-12 h-5 md:h-6 mr-2 shrink-0 "
>
{
showLoader
?
(
<
Loading
className
=
"w-2 md:w-4"
/>
)
:
(
`
${
getStepValue
()}
/
${
TOTAL_DEMO_STEPS
}
`
)
}
</
span
>
);
}
demo/frontend/src/common/components/toolbar/ToolbarSection.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
PropsWithChildren
}
from
'
react
'
;
type
Props
=
PropsWithChildren
<
{
title
:
string
;
borderBottom
?:
boolean
;
}
>
;
export
default
function
ToolbarSection
({
children
,
title
,
borderBottom
=
false
,
}:
Props
)
{
return
(
<
div
className
=
{
`p-6
${
borderBottom
&&
'
border-b border-black
'
}
`
}
>
<
div
className
=
"font-bold ml-2"
>
{
title
}
</
div
>
<
div
className
=
"grid grid-cols-4 gap-2 mt-2 md:mt-6"
>
{
children
}
</
div
>
</
div
>
);
}
demo/frontend/src/common/components/toolbar/useListenToStreamingState.ts
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
StreamingStateUpdateEvent
}
from
'
@/common/components/video/VideoWorkerBridge
'
;
import
useVideo
from
'
@/common/components/video/editor/useVideo
'
;
import
{
StreamingState
}
from
'
@/common/tracker/Tracker
'
;
import
{
isStreamingAtom
,
streamingStateAtom
}
from
'
@/demo/atoms
'
;
import
{
useAtom
}
from
'
jotai
'
;
import
{
useEffect
}
from
'
react
'
;
export
default
function
useListenToStreamingState
():
{
isStreaming
:
boolean
;
streamingState
:
StreamingState
;
}
{
const
[
streamingState
,
setStreamingState
]
=
useAtom
(
streamingStateAtom
);
const
[
isStreaming
,
setIsStreaming
]
=
useAtom
(
isStreamingAtom
);
const
video
=
useVideo
();
useEffect
(()
=>
{
function
onStreamingStateUpdate
(
event
:
StreamingStateUpdateEvent
)
{
setStreamingState
(
event
.
state
);
}
function
onStreamingStarted
()
{
setIsStreaming
(
true
);
}
function
onStreamingCompleted
()
{
setIsStreaming
(
false
);
}
video
?.
addEventListener
(
'
streamingStateUpdate
'
,
onStreamingStateUpdate
);
video
?.
addEventListener
(
'
streamingStarted
'
,
onStreamingStarted
);
video
?.
addEventListener
(
'
streamingCompleted
'
,
onStreamingCompleted
);
return
()
=>
{
video
?.
removeEventListener
(
'
streamingStateUpdate
'
,
onStreamingStateUpdate
,
);
video
?.
removeEventListener
(
'
streamingStarted
'
,
onStreamingStarted
);
video
?.
removeEventListener
(
'
streamingCompleted
'
,
onStreamingCompleted
);
};
},
[
video
,
setStreamingState
,
setIsStreaming
]);
return
{
isStreaming
,
streamingState
};
}
demo/frontend/src/common/components/toolbar/useToolbarTabs.ts
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
toolbarTabIndex
}
from
'
@/demo/atoms
'
;
import
{
useAtom
}
from
'
jotai
'
;
type
State
=
[
tabIndex
:
number
,
setTabIndex
:
(
tabIndex
:
number
)
=>
void
];
export
default
function
useToolbarTabs
():
State
{
return
useAtom
(
toolbarTabIndex
);
}
demo/frontend/src/common/components/useFunctionThrottle.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
useCallback
,
useState
}
from
'
react
'
;
type
ThrottleOptions
=
{
enableThrottling
?:
boolean
;
};
type
State
=
{
isThrottled
:
boolean
;
maxThrottles
:
boolean
;
throttle
:
(
callback
:
()
=>
void
,
options
?:
ThrottleOptions
)
=>
void
;
};
export
default
function
useFunctionThrottle
(
initialDelay
:
number
,
numThrottles
:
number
,
):
State
{
const
[
isThrottled
,
setIsThrottled
]
=
useState
<
boolean
>
(
false
);
const
[
lastClickTime
,
setLastClickTime
]
=
useState
<
number
|
null
>
(
null
);
const
[
numTimesThrottled
,
setNumTimesThrottled
]
=
useState
<
number
>
(
1
);
/**
* The following function's callback gets throttled when the time between two
* executions is less than a threshold.
*
* The threshold is calculated linearly by multiplying the initial delay
* and the number of times the button has been throttled. The button can be
* throttled up to numThrottles times.
*
* The function has an optional flag - enableThrottling - which allows a callsite
* to optionally disable throttling. This is useful in cases where throttling may
* not be necessary. (e.g. for the Track & Play button, we would only like to
* throttle after a stream is aborted.)
*/
const
throttle
=
useCallback
(
(
callback
:
()
=>
void
,
options
:
ThrottleOptions
=
{
enableThrottling
:
true
,
},
)
=>
{
if
(
isThrottled
)
{
return
;
}
const
currentTime
=
Date
.
now
();
if
(
lastClickTime
==
null
)
{
callback
();
setLastClickTime
(
currentTime
);
return
;
}
const
timeBetweenClicks
=
currentTime
-
lastClickTime
;
const
delay
=
initialDelay
*
numTimesThrottled
;
const
shouldThrottle
=
options
.
enableThrottling
&&
delay
>
timeBetweenClicks
;
if
(
shouldThrottle
)
{
setIsThrottled
(
true
);
setTimeout
(()
=>
{
setIsThrottled
(
false
);
},
delay
);
setNumTimesThrottled
(
prev
=>
{
return
prev
===
numThrottles
?
numThrottles
:
prev
+
1
;
});
}
callback
();
setLastClickTime
(
currentTime
);
},
[
initialDelay
,
numThrottles
,
isThrottled
,
lastClickTime
,
numTimesThrottled
],
);
return
{
isThrottled
,
maxThrottles
:
numTimesThrottled
===
numThrottles
,
throttle
,
};
}
demo/frontend/src/common/components/video/ChangeVideoModal.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
type
{
VideoGalleryTriggerProps
}
from
'
@/common/components/gallery/DemoVideoGalleryModal
'
;
import
DemoVideoGalleryModal
from
'
@/common/components/gallery/DemoVideoGalleryModal
'
;
import
useVideo
from
'
@/common/components/video/editor/useVideo
'
;
import
Logger
from
'
@/common/logger/Logger
'
;
import
{
isStreamingAtom
,
uploadingStateAtom
,
VideoData
}
from
'
@/demo/atoms
'
;
import
{
useAtomValue
,
useSetAtom
}
from
'
jotai
'
;
import
{
ComponentType
,
useCallback
}
from
'
react
'
;
import
{
useNavigate
}
from
'
react-router-dom
'
;
type
Props
=
{
videoGalleryModalTrigger
?:
ComponentType
<
VideoGalleryTriggerProps
>
;
showUploadInGallery
?:
boolean
;
onChangeVideo
?:
()
=>
void
;
};
export
default
function
ChangeVideoModal
({
videoGalleryModalTrigger
:
VideoGalleryModalTriggerComponent
,
showUploadInGallery
=
true
,
onChangeVideo
,
}:
Props
)
{
const
isStreaming
=
useAtomValue
(
isStreamingAtom
);
const
setUploadingState
=
useSetAtom
(
uploadingStateAtom
);
const
video
=
useVideo
();
const
navigate
=
useNavigate
();
const
handlePause
=
useCallback
(()
=>
{
video
?.
pause
();
},
[
video
]);
function
handlePauseOrAbortVideo
()
{
if
(
isStreaming
)
{
video
?.
abortStreamMasks
();
}
else
{
handlePause
();
}
}
function
handleSwitchVideos
(
video
:
VideoData
)
{
// Retain any search parameter
navigate
(
{
pathname
:
location
.
pathname
,
search
:
location
.
search
,
},
{
state
:
{
video
,
},
},
);
onChangeVideo
?.();
}
function
handleUploadVideoError
(
error
:
Error
)
{
setUploadingState
(
'
error
'
);
Logger
.
error
(
error
);
}
return
(
<
DemoVideoGalleryModal
trigger
=
{
VideoGalleryModalTriggerComponent
}
showUploadInGallery
=
{
showUploadInGallery
}
onOpen
=
{
handlePauseOrAbortVideo
}
onSelect
=
{
handleSwitchVideos
}
onUploadVideoError
=
{
handleUploadVideoError
}
/>
);
}
demo/frontend/src/common/components/video/EventEmitter.ts
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
type
EventMap
<
WorkerEventMap
>
=
{
type
:
keyof
WorkerEventMap
;
listener
:
(
ev
:
WorkerEventMap
[
keyof
WorkerEventMap
])
=>
unknown
;
};
export
class
EventEmitter
<
WorkerEventMap
>
{
listeners
:
EventMap
<
WorkerEventMap
>
[]
=
[];
trigger
<
K
extends
keyof
WorkerEventMap
>
(
type
:
K
,
ev
:
WorkerEventMap
[
K
])
{
this
.
listeners
.
filter
(
listener
=>
type
===
listener
.
type
)
.
forEach
(({
listener
})
=>
{
setTimeout
(()
=>
listener
(
ev
),
0
);
});
}
addEventListener
<
K
extends
keyof
WorkerEventMap
>
(
type
:
K
,
listener
:
(
ev
:
WorkerEventMap
[
K
])
=>
unknown
,
):
void
{
// @ts-expect-error Incorrect typing. Not sure how to correctly type it
this
.
listeners
.
push
({
type
,
listener
});
}
removeEventListener
<
K
extends
keyof
WorkerEventMap
>
(
type
:
K
,
listener
:
(
ev
:
WorkerEventMap
[
K
])
=>
unknown
,
):
void
{
this
.
listeners
=
this
.
listeners
.
filter
(
existingListener
=>
!
(
existingListener
.
type
===
type
&&
existingListener
.
listener
===
listener
),
);
}
destroy
()
{
this
.
listeners
.
length
=
0
;
}
}
demo/frontend/src/common/components/video/Video.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
BaseTracklet
,
SegmentationPoint
}
from
'
@/common/tracker/Tracker
'
;
import
{
TrackerOptions
,
Trackers
}
from
'
@/common/tracker/Trackers
'
;
import
{
PauseFilled
,
PlayFilledAlt
}
from
'
@carbon/icons-react
'
;
import
stylex
,
{
StyleXStyles
}
from
'
@stylexjs/stylex
'
;
import
{
CSSProperties
,
forwardRef
,
useEffect
,
useImperativeHandle
,
useMemo
,
useRef
,
}
from
'
react
'
;
import
{
Button
}
from
'
react-daisyui
'
;
import
{
EffectIndex
,
Effects
}
from
'
@/common/components/video/effects/Effects
'
;
import
useReportError
from
'
@/common/error/useReportError
'
;
import
Logger
from
'
@/common/logger/Logger
'
;
import
{
isPlayingAtom
,
isVideoLoadingAtom
}
from
'
@/demo/atoms
'
;
import
{
color
}
from
'
@/theme/tokens.stylex
'
;
import
{
useAtom
}
from
'
jotai
'
;
import
useResizeObserver
from
'
use-resize-observer
'
;
import
VideoLoadingOverlay
from
'
./VideoLoadingOverlay
'
;
import
{
StreamingStateUpdateEvent
,
VideoWorkerEventMap
,
}
from
'
./VideoWorkerBridge
'
;
import
{
EffectOptions
}
from
'
./effects/Effect
'
;
import
useVideoWorker
from
'
./useVideoWorker
'
;
const
styles
=
stylex
.
create
({
container
:
{
position
:
'
relative
'
,
width
:
'
100%
'
,
height
:
'
100%
'
,
},
canvasContainer
:
{
display
:
'
flex
'
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
backgroundColor
:
color
[
'
gray-800
'
],
width
:
'
100%
'
,
height
:
'
100%
'
,
},
controls
:
{
position
:
'
absolute
'
,
bottom
:
0
,
left
:
0
,
width
:
'
100%
'
,
padding
:
8
,
background
:
'
linear-gradient(#00000000, #000000ff)
'
,
},
controlButton
:
{
color
:
'
white
'
,
},
});
type
Props
=
{
src
:
string
;
width
:
number
;
height
:
number
;
loading
?:
boolean
;
containerStyle
?:
StyleXStyles
<
{
position
:
CSSProperties
[
'
position
'
];
}
>
;
canvasStyle
?:
StyleXStyles
<
{
width
:
CSSProperties
[
'
width
'
];
}
>
;
controls
?:
boolean
;
createVideoWorker
?:
()
=>
Worker
;
};
export
type
VideoRef
=
{
getCanvas
():
HTMLCanvasElement
|
null
;
get
width
():
number
;
get
height
():
number
;
get
frame
():
number
;
set
frame
(
index
:
number
);
get
numberOfFrames
():
number
;
play
():
void
;
pause
():
void
;
stop
():
void
;
previousFrame
():
void
;
nextFrame
():
void
;
setEffect
(
name
:
keyof
Effects
,
index
:
EffectIndex
,
options
?:
EffectOptions
,
):
void
;
encode
():
void
;
streamMasks
():
void
;
abortStreamMasks
():
Promise
<
void
>
;
addEventListener
<
K
extends
keyof
VideoWorkerEventMap
>
(
type
:
K
,
listener
:
(
ev
:
VideoWorkerEventMap
[
K
])
=>
unknown
,
):
void
;
removeEventListener
<
K
extends
keyof
VideoWorkerEventMap
>
(
type
:
K
,
listener
:
(
ev
:
VideoWorkerEventMap
[
K
])
=>
unknown
,
):
void
;
createFilmstrip
(
width
:
number
,
height
:
number
):
Promise
<
ImageBitmap
>
;
// Tracker
initializeTracker
(
name
:
keyof
Trackers
,
options
?:
TrackerOptions
):
void
;
startSession
(
videoUrl
:
string
):
Promise
<
string
|
null
>
;
closeSession
():
void
;
logAnnotations
():
void
;
createTracklet
():
Promise
<
BaseTracklet
>
;
deleteTracklet
(
trackletId
:
number
):
Promise
<
void
>
;
updatePoints
(
trackletId
:
number
,
points
:
SegmentationPoint
[]):
void
;
clearPointsInVideo
():
Promise
<
boolean
>
;
getWorker_ONLY_USE_WITH_CAUTION
():
Worker
;
};
export
default
forwardRef
<
VideoRef
,
Props
>
(
function
Video
(
{
src
,
width
,
height
,
containerStyle
,
canvasStyle
,
createVideoWorker
,
controls
=
false
,
loading
=
false
,
},
ref
,
)
{
const
reportError
=
useReportError
();
const
canvasRef
=
useRef
<
HTMLCanvasElement
>
(
null
);
const
[
isPlaying
,
setIsPlaying
]
=
useAtom
(
isPlayingAtom
);
const
[
isVideoLoading
,
setIsVideoLoading
]
=
useAtom
(
isVideoLoadingAtom
);
const
bridge
=
useVideoWorker
(
src
,
canvasRef
,
{
createVideoWorker
,
});
const
{
ref
:
resizeObserverRef
,
width
:
resizeWidth
=
1
,
height
:
resizeHeight
=
1
,
}
=
useResizeObserver
<
HTMLDivElement
>
();
const
canvasHeight
=
useMemo
(()
=>
{
const
resizeRatio
=
resizeWidth
/
width
;
return
Math
.
min
(
height
*
resizeRatio
,
resizeHeight
);
},
[
resizeWidth
,
height
,
width
,
resizeHeight
]);
useImperativeHandle
(
ref
,
()
=>
({
getCanvas
()
{
return
canvasRef
.
current
;
},
get
width
()
{
return
bridge
.
width
;
},
get
height
()
{
return
bridge
.
width
;
},
get
frame
()
{
return
bridge
.
frame
;
},
set
frame
(
index
:
number
)
{
bridge
.
frame
=
index
;
},
get
numberOfFrames
()
{
return
bridge
.
numberOfFrames
;
},
play
():
void
{
bridge
.
play
();
},
pause
():
void
{
bridge
.
pause
();
},
stop
():
void
{
bridge
.
stop
();
},
previousFrame
():
void
{
bridge
.
previousFrame
();
},
nextFrame
():
void
{
bridge
.
nextFrame
();
},
setEffect
(
name
:
keyof
Effects
,
index
:
number
,
options
?:
EffectOptions
,
):
void
{
bridge
.
setEffect
(
name
,
index
,
options
);
},
encode
():
void
{
bridge
.
encode
();
},
streamMasks
():
void
{
bridge
.
streamMasks
();
},
abortStreamMasks
():
Promise
<
void
>
{
return
bridge
.
abortStreamMasks
();
},
addEventListener
<
K
extends
keyof
VideoWorkerEventMap
>
(
type
:
K
,
listener
:
(
ev
:
VideoWorkerEventMap
[
K
])
=>
unknown
,
):
void
{
bridge
.
addEventListener
(
type
,
listener
);
},
removeEventListener
<
K
extends
keyof
VideoWorkerEventMap
>
(
type
:
K
,
listener
:
(
ev
:
VideoWorkerEventMap
[
K
])
=>
unknown
,
):
void
{
bridge
.
removeEventListener
(
type
,
listener
);
},
createFilmstrip
(
width
:
number
,
height
:
number
):
Promise
<
ImageBitmap
>
{
return
bridge
.
createFilmstrip
(
width
,
height
);
},
// Tracker
initializeTracker
(
name
:
keyof
Trackers
,
options
:
TrackerOptions
):
void
{
bridge
.
initializeTracker
(
name
,
options
);
},
startSession
(
videoUrl
:
string
):
Promise
<
string
|
null
>
{
return
bridge
.
startSession
(
videoUrl
);
},
closeSession
():
void
{
bridge
.
closeSession
();
},
logAnnotations
():
void
{
bridge
.
logAnnotations
();
},
createTracklet
():
Promise
<
BaseTracklet
>
{
return
bridge
.
createTracklet
();
},
deleteTracklet
(
trackletId
:
number
):
Promise
<
void
>
{
return
bridge
.
deleteTracklet
(
trackletId
);
},
updatePoints
(
trackletId
:
number
,
points
:
SegmentationPoint
[]):
void
{
bridge
.
updatePoints
(
trackletId
,
points
);
},
clearPointsInVideo
():
Promise
<
boolean
>
{
return
bridge
.
clearPointsInVideo
();
},
getWorker_ONLY_USE_WITH_CAUTION
()
{
return
bridge
.
getWorker_ONLY_USE_WITH_CAUTION
();
},
}),
[
bridge
],
);
// Handle video playback events (get playback state to main thread)
useEffect
(()
=>
{
let
isPlaying
=
false
;
function
onFocus
()
{
// Workaround for Safari where the video frame renders black on
// unknown events. Trigger re-render frame on focus.
if
(
!
isPlaying
)
{
bridge
.
goToFrame
(
bridge
.
frame
);
}
}
function
onVisibilityChange
()
{
// Workaround for Safari where the video frame renders black on
// visibility change hidden. Returning to visible shows a black
// frame instead of rendering the current frame.
if
(
document
.
visibilityState
===
'
visible
'
&&
!
isPlaying
)
{
bridge
.
goToFrame
(
bridge
.
frame
);
}
}
function
onError
(
event
:
ErrorEvent
)
{
const
error
=
event
.
error
;
Logger
.
error
(
error
);
reportError
(
error
);
}
function
onPlay
()
{
isPlaying
=
true
;
setIsPlaying
(
true
);
}
function
onPause
()
{
isPlaying
=
false
;
setIsPlaying
(
false
);
}
function
onStreamingDone
(
event
:
StreamingStateUpdateEvent
)
{
// continue to play after streaming is done (state is "full")
if
(
event
.
state
===
'
full
'
)
{
bridge
.
play
();
}
}
function
onLoadStart
()
{
setIsVideoLoading
(
true
);
}
function
onDecodeStart
()
{
setIsVideoLoading
(
false
);
}
window
.
addEventListener
(
'
focus
'
,
onFocus
);
window
.
addEventListener
(
'
visibilitychange
'
,
onVisibilityChange
);
bridge
.
addEventListener
(
'
error
'
,
onError
);
bridge
.
addEventListener
(
'
play
'
,
onPlay
);
bridge
.
addEventListener
(
'
pause
'
,
onPause
);
bridge
.
addEventListener
(
'
streamingStateUpdate
'
,
onStreamingDone
);
bridge
.
addEventListener
(
'
loadstart
'
,
onLoadStart
);
bridge
.
addEventListener
(
'
decode
'
,
onDecodeStart
);
return
()
=>
{
window
.
removeEventListener
(
'
focus
'
,
onFocus
);
window
.
removeEventListener
(
'
visibilitychange
'
,
onVisibilityChange
);
bridge
.
removeEventListener
(
'
error
'
,
onError
);
bridge
.
removeEventListener
(
'
play
'
,
onPlay
);
bridge
.
removeEventListener
(
'
pause
'
,
onPause
);
bridge
.
removeEventListener
(
'
streamingStateUpdate
'
,
onStreamingDone
);
bridge
.
removeEventListener
(
'
loadstart
'
,
onLoadStart
);
bridge
.
removeEventListener
(
'
decode
'
,
onDecodeStart
);
};
},
[
bridge
,
reportError
,
setIsPlaying
,
setIsVideoLoading
]);
return
(
<
div
{
...
stylex
.
props
(
containerStyle
??
styles
.
container
)
}
ref
=
{
resizeObserverRef
}
>
<
div
{
...
stylex
.
props
(
styles
.
canvasContainer
)
}
>
{
(
isVideoLoading
||
loading
)
&&
<
VideoLoadingOverlay
/>
}
<
canvas
ref
=
{
canvasRef
}
{
...
stylex
.
props
(
canvasStyle
)
}
className
=
"lg:rounded-[4px]"
width
=
{
width
}
height
=
{
height
}
style
=
{
{
height
:
canvasHeight
,
}
}
/>
</
div
>
{
controls
&&
(
<
div
{
...
stylex
.
props
(
styles
.
controls
)
}
>
<
Button
color
=
"ghost"
size
=
"xs"
startIcon
=
{
isPlaying
?
(
<
PauseFilled
{
...
stylex
.
props
(
styles
.
controlButton
)
}
size
=
{
14
}
/>
)
:
(
<
PlayFilledAlt
{
...
stylex
.
props
(
styles
.
controlButton
)
}
size
=
{
14
}
/>
)
}
onClick
=
{
()
=>
{
isPlaying
?
bridge
.
pause
()
:
bridge
.
play
();
}
}
/>
</
div
>
)
}
</
div
>
);
});
demo/frontend/src/common/components/video/VideoFilmstripWithPlayback.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
PlaybackButton
from
'
@/common/components/button/PlaybackButton
'
;
import
VideoFilmstrip
from
'
@/common/components/video/filmstrip/VideoFilmstrip
'
;
import
{
spacing
,
w
}
from
'
@/theme/tokens.stylex
'
;
import
stylex
from
'
@stylexjs/stylex
'
;
const
styles
=
stylex
.
create
({
container
:
{
display
:
'
flex
'
,
alignItems
:
'
end
'
,
gap
:
spacing
[
4
],
paddingHorizontal
:
spacing
[
4
],
width
:
'
100%
'
,
},
playbackButtonContainer
:
{
display
:
'
flex
'
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
width
:
w
[
12
],
height
:
w
[
12
],
},
filmstripContainer
:
{
flexGrow
:
1
,
},
});
export
default
function
VideoFilmstripWithPlayback
()
{
return
(
<
div
{
...
stylex
.
props
(
styles
.
container
)
}
>
<
div
{
...
stylex
.
props
(
styles
.
playbackButtonContainer
)
}
>
<
PlaybackButton
/>
</
div
>
<
div
{
...
stylex
.
props
(
styles
.
filmstripContainer
)
}
>
<
VideoFilmstrip
/>
</
div
>
</
div
>
);
}
demo/frontend/src/common/components/video/VideoLoadingOverlay.tsx
0 → 100644
View file @
17d316f3
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import
{
fontSize
,
fontWeight
,
spacing
}
from
'
@/theme/tokens.stylex
'
;
import
stylex
from
'
@stylexjs/stylex
'
;
import
{
Loading
}
from
'
react-daisyui
'
;
const
styles
=
stylex
.
create
({
overlay
:
{
position
:
'
absolute
'
,
width
:
'
100%
'
,
height
:
'
100%
'
,
background
:
'
rgba(0,0,0,0.5)
'
,
},
indicatorContainer
:
{
position
:
'
absolute
'
,
top
:
'
50%
'
,
left
:
'
50%
'
,
transform
:
'
translate(-50%, -50%)
'
,
display
:
'
flex
'
,
alignItems
:
'
center
'
,
gap
:
spacing
[
4
],
color
:
'
white
'
,
},
indicatorText
:
{
color
:
'
white
'
,
fontSize
:
fontSize
[
'
sm
'
],
fontWeight
:
fontWeight
[
'
medium
'
],
},
});
type
Props
=
{
label
?:
string
;
};
export
default
function
VideoLoadingOverlay
({
label
}:
Props
)
{
return
(
<
div
{
...
stylex
.
props
(
styles
.
overlay
)
}
>
<
div
{
...
stylex
.
props
(
styles
.
indicatorContainer
)
}
>
<
Loading
size
=
"sm"
/>
<
div
{
...
stylex
.
props
(
styles
.
indicatorText
)
}
>
{
label
??
'
Loading video...
'
}
</
div
>
</
div
>
</
div
>
);
}
Prev
1
…
23
24
25
26
27
28
29
30
31
…
48
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment