Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Add `Page.pop_views_until()` to pop multiple views and return a result to the destination view ([#6326](https://github.com/flet-dev/flet/issues/6326), [#6347](https://github.com/flet-dev/flet/pull/6347)) by @brunobrown.
* Make `NavigationDrawerDestination.label` accept custom controls and add `NavigationDrawerTheme.icon_theme` ([#6379](https://github.com/flet-dev/flet/issues/6379), [#6395](https://github.com/flet-dev/flet/pull/6395)) by @ndonkoHenri.
* Add `local_position` and `global_position` to `DragTargetEvent`, deprecating `x`, `y`, and `offset` ([#6387](https://github.com/flet-dev/flet/issues/6387), [#6401](https://github.com/flet-dev/flet/pull/6401)) by @ndonkoHenri.
* Add `content` property to `Video` control ([#6392](https://github.com/flet-dev/flet/pull/6392)) by @bl1nch.

### Improvements

Expand Down
95 changes: 95 additions & 0 deletions sdk/python/examples/controls/video/custom-content/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import flet as ft
import flet_video as ftv

sample_media = [
ftv.VideoMedia(
"https://user-images.githubusercontent.com/28951144/229373720-14d69157-1a56-4a78-a2f4-d7a134d7c3e9.mp4"
),
ftv.VideoMedia(
"https://user-images.githubusercontent.com/28951144/229373718-86ce5e1d-d195-45d5-baa6-ef94041d0b90.mp4"
),
ftv.VideoMedia(
"https://user-images.githubusercontent.com/28951144/229373716-76da0a4e-225a-44e4-9ee7-3e9006dbc3e3.mp4"
),
ftv.VideoMedia(
"https://user-images.githubusercontent.com/28951144/229373695-22f88f13-d18f-4288-9bf1-c3e078d83722.mp4"
),
ftv.VideoMedia(
"https://user-images.githubusercontent.com/28951144/229373709-603a7a89-2105-4e1b-a5a5-a6c3567c9a59.mp4",
extras={
"artist": "Thousand Foot Krutch",
"album": "The End Is Where We Begin",
},
http_headers={
"Foo": "Bar",
"Accept": "*/*",
},
),
]


async def main(page: ft.Page):
page.spacing = 20

video = ft.Ref[ftv.Video]()
title = ft.Ref[ft.Text]()

async def handle_pause(e: ft.Event[ft.Button]):
await video.current.pause()

async def handle_play_or_pause(e: ft.Event[ft.Button]):
await video.current.play_or_pause()

async def handle_play(e: ft.Event[ft.Button]):
await video.current.play()

async def handle_stop(e: ft.Event[ft.Button]):
await video.current.stop()

async def handle_next(e: ft.Event[ft.Button]):
await video.current.next()

async def handle_previous(e: ft.Event[ft.Button]):
await video.current.previous()

def handle_track_change(e: ft.Event[ftv.Video]):
title.current.value = sample_media[e.data].resource

page.add(
ftv.Video(
width=640,
height=360,
ref=video,
playlist=sample_media,
on_track_change=handle_track_change,
content=ft.Container(
padding=ft.Padding.all(10),
expand=True,
content=ft.Column(
controls=[
ft.Container(
padding=ft.Padding.all(5),
border_radius=20,
bgcolor=ft.Colors.GREEN_ACCENT_100,
content=ft.Text(size=10, value="", ref=title)
)
],
)
)
),
ft.Row(
wrap=True,
controls=[
ft.Button("Play", on_click=handle_play),
ft.Button("Pause", on_click=handle_pause),
ft.Button("Play Or Pause", on_click=handle_play_or_pause),
ft.Button("Stop", on_click=handle_stop),
ft.Button("Next", on_click=handle_next),
ft.Button("Previous", on_click=handle_previous),
],
)
)


if __name__ == "__main__":
ft.run(main)
26 changes: 26 additions & 0 deletions sdk/python/examples/controls/video/custom-content/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[project]
name = "video-custom-content-example"
version = "1.0.0"
description = "Custom content in a Flet video player."
requires-python = ">=3.10"
keywords = ["video", "media", "playlist", "player", "async"]
authors = [{ name = "Flet team", email = "hello@flet.dev" }]
dependencies = ["flet", "flet-video"]

[dependency-groups]
dev = ["flet-cli", "flet-desktop", "flet-web"]

[tool.flet.gallery]
categories = ["Media/Video"]

[tool.flet.metadata]
title = "Video custom content example"
controls = ["Container", "Column", "Row", "Button", "Text", "Video"]
layout_pattern = "dashboard"
complexity = "basic"
features = ["custom content"]

[tool.flet]
org = "dev.flet"
company = "Flet"
copyright = "Copyright (C) 2023-2026 by Flet"
9 changes: 9 additions & 0 deletions sdk/python/packages/flet-video/src/flet_video/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ class Video(ft.LayoutControl):
Whether to show the video player controls.
"""

content: Optional[ft.Control] = None
"""
The custom content of the video control.
"""

fullscreen: bool = False
"""
Whether the video player is presented in fullscreen mode.
Expand Down Expand Up @@ -181,6 +186,10 @@ class Video(ft.LayoutControl):
the index of the new track.
"""

def init(self):
super().init()
self._internals["host_expanded"] = True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?

def before_update(self):
super().before_update()
if not (0 <= self.volume <= 100):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ class _VideoControlState extends State<VideoControl> with FletStoreMixin {
@override
Widget build(BuildContext context) {
debugPrint("Video build: ${widget.control.id}");
var content = widget.control.buildWidget("content");

var subtitleConfiguration = parseSubtitleConfiguration(
widget.control.get("subtitle_configuration"),
Expand Down Expand Up @@ -239,7 +240,18 @@ class _VideoControlState extends State<VideoControl> with FletStoreMixin {
key: _videoKey,
controller: _controller,
wakelock: widget.control.getBool("wakelock", true)!,
controls: showControls ? AdaptiveVideoControls : null,
controls: showControls
? (state) {
if (content != null) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure hard-coded column wrapper is a good idea. I'd leave content a generic control and let user decide what to put there.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that media_kit forces any widget passed to controls to expand and stretches it to the full area of the player. I thought it would be better to wrap content in a Column before passing it to controls. This way, we create a feeling that the Video control itself is like a separate View.
self._internals["host_expanded"] = True is used so that the widget passed to content supports expand=True, but it is not mandatory for it.
What do you think about this?

children: [content]
);
}
return AdaptiveVideoControls(state);
}
: null,
pauseUponEnteringBackgroundMode:
widget.control.getBool("pause_upon_entering_background_mode", true)!,
resumeUponEnteringForegroundMode: widget.control
Expand Down
Loading