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

Add changes to configurationchange MediaStreamTrack event #83

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

beaufortfrancois
Copy link
Contributor

@beaufortfrancois beaufortfrancois commented Jan 27, 2023

Following #80 (comment), here's my naive attempt to expose changes in the configurationchange MediaStreamTrack event.

Live preview: https://pr-preview.s3.amazonaws.com/beaufortfrancois/mediacapture-extensions/pull/83.html#exposing-change-of-mediastreamtrack-configuration

@youennf PTAL


Preview | Diff

@beaufortfrancois
Copy link
Contributor Author

Gentle ping @youennf

index.html Outdated
<li>
<p>[=Fire an event=] named <var>configurationchange</var> on <var>track</var>.</p>
<li><p>Let <var>changes</var> be an empty [=sequence=].</p></li>
<li><p>For each change in <var>track</var>'s capabilities and settings, add its name to <var>changes</var>.</p></li>
Copy link
Member

Choose a reason for hiding this comment

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

"For each change... add its name"
What is the name of a change? Should that be clarified? (Depending on the answer - the issue was raised of distinguishing capabilities and settings with a similar name. Have I missed the resolution of that discussion?)

Copy link
Contributor

Choose a reason for hiding this comment

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

We tentatively settled on having a single array.
One reason is that both capabilities and settings might change at the same time.
Say bgblur is made mandatory, bgblur capabiliy becomes [true] and settings become true.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Elad, we can probably tidy a bit the language.
Source settings are dictionaries, ditto for capabilities, we could use for describing the matching algorithm, something like:

  • for each key-value pair of source settings, if track settings does not have the same pair, append key to the list if not already in the list.
  • for each key-value pair of track settings, if source settings does not have the same pair, append key to the list if not already in the list.
  • for each key-value pair of source capabilities, if track capabilities does not have the same pair, append key to the list if not already in the list.
  • for each key-value pair of track capabilities, if source set capabilities ings does not have the same pair, append key to the list if not already in the list.
  • If list is empty, abort these steps
  • Sort the list by lexicographical order.
  • Fire the event

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you! It is much better now!

const [track] = stream.getVideoTracks();
track.addEventListener("configurationchange", (event) => {
if (event.changes.includes("backgroundBlur")) {
// Background blur configuration has changed.
Copy link
Member

Choose a reason for hiding this comment

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

Nit, and completely up to you, but maybe a clearer example would also indicate why an app might care about this? Maybe the comment could mention it would toggle its own blurring to be the inverse, so as to always be blurred but never double-blurred?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion!

index.html Outdated
<p>
<p>When the [=User Agent=] detects <dfn data-export id="change-track-configuration">a change of configuration</dfn>
in a <var>track</var>'s underlying source, the [=User Agent=] MUST run the following steps:</p>
<ol>
<li><p>If <var>track</var>.{{MediaStreamTrack/muted}} is <code>true</code>, wait for <var>track</var>.{{MediaStreamTrack/muted}}
to become <code>false</code> or <var>track</var>.{{MediaStreamTrack/readyState}} to be "ended".</p></li>
<li><p>If <var>track</var>.{{MediaStreamTrack/readyState}} is "ended", abort these steps.</p></li>
<li><p>If <var>track</var>'s capabilities, constraints and settings are matching <var>source</var> configuration, abort these steps.
<li><p>If <var>track</var>'s capabilities, constraints and settings are matching <var>source</var> configuration, abort these steps.</li>
Copy link
Contributor

Choose a reason for hiding this comment

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

Thinking more about it, if capabilities and settings are matching, why should constraints change?
Maybe we can remove constraints here (or we could leave that to a follow-up)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I definitely agree.

index.html Outdated
<li>
<p>[=Fire an event=] named <var>configurationchange</var> on <var>track</var>.</p>
<li><p>Let <var>changes</var> be an empty [=sequence=].</p></li>
<li><p>For each change in <var>track</var>'s capabilities and settings, add its name to <var>changes</var>.</p></li>
Copy link
Contributor

Choose a reason for hiding this comment

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

We tentatively settled on having a single array.
One reason is that both capabilities and settings might change at the same time.
Say bgblur is made mandatory, bgblur capabiliy becomes [true] and settings become true.

index.html Outdated
<li>
<p>[=Fire an event=] named <var>configurationchange</var> on <var>track</var>.</p>
<li><p>Let <var>changes</var> be an empty [=sequence=].</p></li>
<li><p>For each change in <var>track</var>'s capabilities and settings, add its name to <var>changes</var>.</p></li>
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Elad, we can probably tidy a bit the language.
Source settings are dictionaries, ditto for capabilities, we could use for describing the matching algorithm, something like:

  • for each key-value pair of source settings, if track settings does not have the same pair, append key to the list if not already in the list.
  • for each key-value pair of track settings, if source settings does not have the same pair, append key to the list if not already in the list.
  • for each key-value pair of source capabilities, if track capabilities does not have the same pair, append key to the list if not already in the list.
  • for each key-value pair of track capabilities, if source set capabilities ings does not have the same pair, append key to the list if not already in the list.
  • If list is empty, abort these steps
  • Sort the list by lexicographical order.
  • Fire the event

index.html Outdated
is an [=event handler IDL attribute=] for the `onconfigurationchange`
[=event handler=], whose [=event handler event type=] is
<dfn event for=MediaStreamTrack>configurationchange</dfn>.
</p>
<p>
<p>When the [=User Agent=] detects <dfn data-export id="change-track-configuration">a change of configuration</dfn>
in a <var>track</var>'s underlying source, the [=User Agent=] MUST run the following steps:</p>
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a pre-existing issue but it is not very clear whether each clone track will fire in its own event loop task or in the same event loop task.
We might want to file an issue about this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've filed an issue at #86.

Copy link
Contributor Author

@beaufortfrancois beaufortfrancois left a comment

Choose a reason for hiding this comment

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

@youennf @eladalon1983 Thanks for your review! I've addressed your feedback.

index.html Outdated
<p>
<p>When the [=User Agent=] detects <dfn data-export id="change-track-configuration">a change of configuration</dfn>
in a <var>track</var>'s underlying source, the [=User Agent=] MUST run the following steps:</p>
<ol>
<li><p>If <var>track</var>.{{MediaStreamTrack/muted}} is <code>true</code>, wait for <var>track</var>.{{MediaStreamTrack/muted}}
to become <code>false</code> or <var>track</var>.{{MediaStreamTrack/readyState}} to be "ended".</p></li>
<li><p>If <var>track</var>.{{MediaStreamTrack/readyState}} is "ended", abort these steps.</p></li>
<li><p>If <var>track</var>'s capabilities, constraints and settings are matching <var>source</var> configuration, abort these steps.
<li><p>If <var>track</var>'s capabilities, constraints and settings are matching <var>source</var> configuration, abort these steps.</li>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I definitely agree.

const [track] = stream.getVideoTracks();
track.addEventListener("configurationchange", (event) => {
if (event.changes.includes("backgroundBlur")) {
// Background blur configuration has changed.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion!

index.html Outdated
is an [=event handler IDL attribute=] for the `onconfigurationchange`
[=event handler=], whose [=event handler event type=] is
<dfn event for=MediaStreamTrack>configurationchange</dfn>.
</p>
<p>
<p>When the [=User Agent=] detects <dfn data-export id="change-track-configuration">a change of configuration</dfn>
in a <var>track</var>'s underlying source, the [=User Agent=] MUST run the following steps:</p>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've filed an issue at #86.

index.html Outdated
<li>
<p>[=Fire an event=] named <var>configurationchange</var> on <var>track</var>.</p>
<li><p>Let <var>changes</var> be an empty [=sequence=].</p></li>
<li><p>For each change in <var>track</var>'s capabilities and settings, add its name to <var>changes</var>.</p></li>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you! It is much better now!

<li><p>If <var>changes</var> [=list/is empty=], abort these steps.</p></li>
<li><p>[=list/Sort=] <var>changes</var> in lexicographical order.</p></li>
<li>
<p>[=Fire an event=] named {{MediaStreamTrack/configurationchange}}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we should also specify the boolean values of bubbles and cancellable for this event. I'd say they should be both false. Do you agree?

Copy link
Contributor

Choose a reason for hiding this comment

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

These are the default values in EventInit so we might not need it.

index.html Show resolved Hide resolved
@youennf
Copy link
Contributor

youennf commented Feb 2, 2023

Let's put this on the agenda for next interim.

@alvestrand
Copy link
Contributor

Not clear to me what adopting this change means for other attributes than background blur - for instance, if a captured tab is resized, its size attributes change - does the event fire? and when?

@jan-ivar
Copy link
Member

jan-ivar commented Feb 2, 2023

§ 7.7. Use plain Events for state says we generally want to fire plain events and not add state to events. It seems to me, the app can figure out what changed without this information.

For example, on devicechange we DON'T have information on the event about which device(s) were added or which were removed, as this is simple enough to figure out and would get complex real fast.

@eladalon1983
Copy link
Member

§ 7.7. Use plain Events for state says we generally want to fire plain events and not add state to events. It seems to me, the app can figure out what changed without this information.

Arguably, an app can more efficiently and elegantly bail early on unimportant changes if it can query the event for "is x among the properties changed" rather than locally store state and compare it to the new state, which it needs to call getSettings() for.

Contrast:

if (!event.changes.includes("backgroundBlur")) {
  return;
}
respondToNewState(event.target.getSettings().backgroundBlur);

With:

const settings = event.target.getSettings();
if (settings.backgroundBlur == previousState.backgroundBlur) {
  return;
}
previousState = settings;
respondToNewState(settings.backgroundBlur);

@youennf
Copy link
Contributor

youennf commented Feb 6, 2023

§ 7.7. Use plain Events for state says we generally want to fire plain events and not add state to events. It seems to me, the app can figure out what changed without this information.

It is true the app can figure this out on its own.
On the other hand, I am also sympathetic to the desire to quickly identify what changed.
The main reason is configurationchange is a single event for many different change types.
An alternative is to have an event per settings/capabilities (like click events somehow do) but that seems overkill to me.

For example, on devicechange we DON'T have information on the event about which device(s) were added or which were removed, as this is simple enough to figure out and would get complex real fast.

The analogy would be more about whether exposing a device-added/device-removed enumeration to the event.
In that case, there are only two alternatives, compared to configurationchange which has many more.

@eehakkin
Copy link
Contributor

eehakkin commented Feb 9, 2023

§ 7.7. Use plain Events for state says we generally want to fire plain events and not add state to events. It seems to me, the app can figure out what changed without this information.

It is true the app can figure this out on its own. On the other hand, I am also sympathetic to the desire to quickly identify what changed. The main reason is configurationchange is a single event for many different change types. An alternative is to have an event per settings/capabilities (like click events somehow do) but that seems overkill to me.

I agree. Overall, this change seems good to me.

@beaufortfrancois
Copy link
Contributor Author

@jan-ivar I agree with #83 (comment) as well.

@jan-ivar
Copy link
Member

jan-ivar commented Feb 9, 2023

This is an event with one name returning a list of other names basically. I think this is the sort of APIs the design guides explicitly tell us to avoid, because it creates new and disparate/novel patterns that are inherently redundant with each other and creates entropy.

I think it's a false choice to say the alternative is an event name per change. This seems easy enough for JS and similar enough to devicechange which does NOT have an event for add and a separate for remove, because JS simply compares the result before and after to learn that. Same here. For example:

let lastSettings = track.getSettings();

track.onconfigurationchange = () => {
  const settings = track.getSettings();
  if (settings.backgroundBlur != lastSettings.backgroundBlur) {
    // background blur changed
  }
  lastSettings = track.getSettings();
};

We should not create new platform APIs for this simple thing. That is my position as well as my interpretation of TAG advice in § 7.7. Use plain Events for state. cc @annevk

@jan-ivar
Copy link
Member

jan-ivar commented Feb 9, 2023

In the interest of progress, isn't this something we can add later if needed rather than block on?

@beaufortfrancois
Copy link
Contributor Author

IIUC we now have two options:

  1. Define an event handler for each type of configuration change. For background blur, it would be onbackgroundblurchange, etc. (See Filtering for relevant configurationchange events #82 (comment))
track.onbackgroundblurchange = () => {
  // Background blur has changed.
  if (track.getSettings().backgroundBlur) {
    // Turn off web app own blurring so that video doesn't get double-blurred.
  }
};
  1. Don't include changes in configurationchange event and let web developers figure out by themselves if the event matters to them.
let lastSettings = track.getSettings();

track.onconfigurationchange = () => {
  const settings = track.getSettings();
  if (settings.backgroundBlur != lastSettings.backgroundBlur) {
    // Turn off web app own blurring so that video doesn't get double-blurred.
  }
  lastSettings = track.getSettings();
};

Am I missing another option?

@eehakkin
Copy link
Contributor

  1. Define an event handler for each type of configuration change. For background blur, it would be onbackgroundblurchange, etc. (See Filtering for relevant configurationchange events #82 (comment))

I am not very fond of this approach. It either gets really complex (there are 37 constrainable properties in total defined in MediaTrackSettings at least on Chrome) or it treats constrainable properties differently from each other (like if there were event only say for background blur).

  1. Don't include changes in configurationchange event and let web developers figure out by themselves if the event matters to them.

I am quite fine with not including changes in configurationchange event. It adds some avoidable work to event handlers but it is still managable. It might also require some clarification, such as that the event should not be triggered if the frameRate setting is not a real setting but an estimate which is about to change on every or always every frame or if the color temperature changes during auto white balance mode, etc.

Am I missing another option?

In #82 (comment) you suggested a MediaStreamTrackObserver based on the Observer web pattern. That might be an overkill but it would handle everything nicely including changed frame rate estimates.

@annevk
Copy link
Member

annevk commented Feb 15, 2023

FWIW, this API-shape doesn't look unreasonable to me. Events can come with accompanying data that explains the nature of the change. E.g., the recently-added HTML popover feature exposes the old and new states in its beforetoggle/toggle events. That can be especially useful if you want to coalesce events, but I also don't think we strive to avoid redundancy at all costs. Developer ergonomics are important too.

@beaufortfrancois
Copy link
Contributor Author

According to #83 (comment), it seems like we could actually expose changes to configurationchange event.

@youennf @jan-ivar What are the next steps?

@alvestrand
Copy link
Contributor

For discussion in the WG in March?

@jan-ivar
Copy link
Member

jan-ivar commented Mar 2, 2023

the recently-added HTML popover feature exposes the old and new states in its beforetoggle/toggle events.

MST changes its settings inside the queued task whereas https://html.spec.whatwg.org/#queue-a-popover-toggle-event-task appears to have a different event firing model where the event fires AFTER the state transition (which isn't in the queued task).

So oldState in that case isn't trivially available on the event target like in our case, if I read it right. Getting git blame on html is a pain, so I haven't yet dug out whether that was part of its rationale, but it seems to exist not solely for developer ergonomics, and also highlights why e.g. adding oldState to the event target would have been weird, since the point there seems to be to disambiguate state transitions that effectively have already happened.

In our case, I've seen no evidence that disambiguating the state transitions of blur is important. It seems apps are mostly interested in the most recent state, which seems trivial to ascertain from the event target, by comparing to the app's tracking of its state of interest.

@annevk
Copy link
Member

annevk commented Mar 2, 2023

That's true for toggle, but not beforetoggle, which uses the same interface.

I think your suggestion above about adding this once web developers have had a chance to write some code against a plainer version is reasonable though. Start with the minimal thing that works and then build up based on demand and patterns that emerge.

@youennf
Copy link
Contributor

youennf commented Mar 2, 2023

Start with the minimal thing that works and then build up based on demand and patterns that emerge.

Makes sense to me.
It seems we can wait for web developer input on this.

@beaufortfrancois
Copy link
Contributor Author

beaufortfrancois commented Mar 2, 2023

FYI I've started #89 to improve the configuration change section.

@youennf youennf added the Icebox Stuff that may be done one day, but not today. label Mar 16, 2023
@abhijeetk
Copy link

@youennf @eladalon1983
For a video stream, What is expected when device orientation changes(portrait to lanscape and vice versa) ?
Should it trigger a configuration change for a video track settings with updated values of width and height of video stream ?

Refer : https://jsfiddle.net/abhijeetk/k9qso14g/5/
After orientation change, Video track setting remanins same but video element dimensions are changing.

@dontcallmedom-bot
Copy link

This issue was discussed in WebRTC Feb 2023 Meeting – (onConfigurationChange 🎞︎)

@dontcallmedom-bot
Copy link

This issue was mentioned in WebRTC Feb 2023 Meeting – (onConfigurationChange 🎞︎)

@jan-ivar
Copy link
Member

jan-ivar commented Feb 22, 2024

Nearly a year old. Is there still interest in proceeding with this approach?

@jan-ivar jan-ivar closed this Feb 22, 2024
@jan-ivar
Copy link
Member

Prematurely closed, sorry.

@jan-ivar jan-ivar reopened this Feb 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Icebox Stuff that may be done one day, but not today.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants