Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Media elements improvements #8203

Merged
merged 16 commits into from Mar 13, 2024
Merged

Media elements improvements #8203

merged 16 commits into from Mar 13, 2024

Conversation

kajarenc
Copy link
Collaborator

@kajarenc kajarenc commented Feb 26, 2024

Describe your changes

Add end_time and loop parameters to st.audio and st.video
Specs:
End time https://www.notion.so/snowflake-corp/Spec-cf910f5811a84a6a9976d230c85a044e
Loop: https://www.notion.so/snowflake-corp/Spec-18bbbb288f4c4a0ba129d95eeb9c0260

GitHub Issue Link (if applicable)

Testing Plan

  • Explanation of why no additional tests are needed
  • Unit Tests (JS and/or Python) Done ✅
  • E2E Tests Done ✅
  • Any manual testing needed?

Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

@kajarenc kajarenc changed the title [WIP] Add loop parameter to st.audio [WIP] Media elements improvements Feb 26, 2024
@snehankekre
Copy link
Collaborator

@kajarenc The end time and looping behavior work as expected, according to the specs 😄 LGTM :shipit:

@kajarenc kajarenc changed the title [WIP] Media elements improvements Media elements improvements Mar 11, 2024
@kajarenc kajarenc marked this pull request as ready for review March 11, 2024 17:48
Copy link
Collaborator

@LukasMasuch LukasMasuch left a comment

Choose a reason for hiding this comment

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

LGTM 👍 What happens if the configured end time or start time is larger than the video time?

audio_element.evaluate("el => el.play()")
app.wait_for_timeout(5000)
expect(audio_element).to_have_js_property("paused", True)
assert int(audio_element.evaluate("el => el.currentTime")) == 13
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: To prevent flakiness we probably need to use wait_until method from conftest. See this as an example:

wait_until(app, check_dimensions)

Also see https://github.com/streamlit/streamlit/wiki/Running-e2e-tests-and-updating-snapshots#three-rules-of-playwright :)

# approximately 17 (4 seconds until end_time and 2 seconds starting from start time)
app.wait_for_timeout(6000)
expect(audio_element).to_have_js_property("paused", False)
assert 16 < audio_element.evaluate("el => el.currentTime") < 18
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above :)

@pytest.mark.parametrize(
"nth_element",
[
pytest.param(5, marks=pytest.mark.skip_browser("webkit")),
Copy link
Collaborator

Choose a reason for hiding this comment

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

interesting :) didn't know this trick 👍

# Wait until video will reach end_time
app.wait_for_timeout(3000)
expect(video_element).to_have_js_property("paused", True)
assert int(video_element.evaluate("el => el.currentTime")) == 33
Copy link
Collaborator

Choose a reason for hiding this comment

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

Replace with wait_until (see comments above)

# 4 seconds until end_time and 2 seconds starting from start time
app.wait_for_timeout(6000)
expect(video_element).to_have_js_property("paused", False)
assert 36 < video_element.evaluate("el => el.currentTime") < 38
Copy link
Collaborator

Choose a reason for hiding this comment

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

Replace with wait_until (see comments above)

if (loop) {
youtubeUrl.searchParams.append("loop", "1")
// When using the loop parameter, YouTube requires the playlist parameter to be set to the same video ID
const videoId = youtubeUrl.pathname.split("/").pop()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does that work for all the different types of youtube URLs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It should work with different youtube URLs, because we standardize URL on the backend

return f"https://www.youtube.com/embed/{code}"

@@ -79,6 +81,10 @@ def audio(
sample_rate: int or None
The sample rate of the audio data in samples per second. Only required if
``data`` is a numpy array.
end_time: int
The time at which this element should stop playing.
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Maybe add that its the number of seconds

Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: in some other time-related parameters (e.g. ttl), we also support timedelta and str (parsed by Pandas) in addition to the number of seconds. Maybe also something to consider here:

image

This could work here as well, but maybe not the best fit

cc @snehankekre

Copy link
Collaborator

Choose a reason for hiding this comment

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

The smallest reasonable measure to configure a video/audio start/end time is probably seconds or? So no need to use float here?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The smallest reasonable measure to configure a video/audio start/end time is probably seconds or? So no need to use float here?

@LukasMasuch The currentTime property of the HTMLMediaElement interface from which HTMLVideoElement inherits is specified in seconds according to the docs: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/currentTime

And it can be set or returned as "a double-precision floating-point value indicating the current playback time in seconds", so we can support float here. Should we do it in this PR or another one scoped specifically to this change?

Copy link
Collaborator

Choose a reason for hiding this comment

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

we also support timedelta and str (parsed by Pandas) in addition to the number of seconds. Maybe also something to consider here

@LukasMasuch really good point! This is a suggestion Thiago also had when we presented these specs in office hours.

Supporting timedelta objects from datetime and a string specifying the time in a format supported by Pandas's Timedelta constructor, in addition to seconds, would be nice for consistency across the API and will reduce the learning curve for new users.

+1 to supporting them.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That would let devs do things like:

import streamlit as st
from datetime import timedelta

start_time_td = timedelta(seconds=15)
end_time_td = timedelta(minutes=2, seconds=30)

st.video(data="video.mp4", start_time=start_time_td, end_time=end_time_td)
# Assuming in our internal implementation we call .total_seconds() on the timedelta objects
import streamlit as st

start_time_str = "15s" 
end_time_str = "2m 30s"

# These values are parsed using Pandas's Timedelta constructor
st.video(data="video.mp4", start_time=start_time_str, end_time=end_time_str)

@kajarenc
Copy link
Collaborator Author

LGTM 👍 What happens if the configured end time or start time is larger than the video time?

We raise an exception if end_time < start_time, but for end time or start time is larger than video length handled by the browser (and behavior is very expected).

If start_time > video.length then the player on the last frame, but when you click play it starts from the beginning of the video.

If end_time > video.length the player stops on the last frame of the video (if loop == True, we continue from start_time).

@kajarenc kajarenc merged commit 8162158 into develop Mar 13, 2024
35 of 36 checks passed
This was linked to issues Mar 14, 2024
zyxue pushed a commit to zyxue/streamlit that referenced this pull request Apr 16, 2024
Add end_time and loop parameters to st.audio and st.video
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create parameter 'end_time' for st.video Loop option for video
3 participants