{"id":19178013,"url":"https://github.com/eyevinn/media-event-filter","last_synced_at":"2025-05-07T20:43:31.119Z","repository":{"id":96978241,"uuid":"560361878","full_name":"Eyevinn/media-event-filter","owner":"Eyevinn","description":"Interpreter for HTML5 media events","archived":false,"fork":false,"pushed_at":"2025-04-19T08:09:58.000Z","size":665621,"stargazers_count":11,"open_issues_count":13,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-05-07T20:43:21.709Z","etag":null,"topics":["events","html5","library","mediasource","mse","typescript","video"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Eyevinn.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-01T10:25:15.000Z","updated_at":"2025-04-19T08:10:01.000Z","dependencies_parsed_at":"2024-01-19T10:23:53.265Z","dependency_job_id":"b8e7575d-d0d8-478a-843a-d042b626c8e6","html_url":"https://github.com/Eyevinn/media-event-filter","commit_stats":{"total_commits":87,"total_committers":3,"mean_commits":29.0,"dds":0.1724137931034483,"last_synced_commit":"feb6da0af239fed93fc4b441a8507d55abd15c13"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Eyevinn%2Fmedia-event-filter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Eyevinn%2Fmedia-event-filter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Eyevinn%2Fmedia-event-filter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Eyevinn%2Fmedia-event-filter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Eyevinn","download_url":"https://codeload.github.com/Eyevinn/media-event-filter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252954140,"owners_count":21830894,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["events","html5","library","mediasource","mse","typescript","video"],"created_at":"2024-11-09T10:36:12.312Z","updated_at":"2025-05-07T20:43:31.089Z","avatar_url":"https://github.com/Eyevinn.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Media Event Filter\n\nA dependecy free package, providing [HTMLMediaElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement) event interpretation: a single source of truth for UI state and KPI tracking, equal across web platforms.\n\nWorks in modern browsers, including environments like Chromecast and Smart TV (LG, Samsung).\n\nWorks together with popular player engines: [shaka](https://github.com/shaka-project/shaka-player), [hls.js](https://github.com/video-dev/hls.js/), [dash.js](https://github.com/Dash-Industry-Forum/dash.js), and native MSE video playback.\n\n## Why\n\nMake video playback UI state and KPI tracking easy. \n\nThe HTML5 video element does not produce a consistent set of events that correspond to the updates required by trackers and user interfaces, like `loaded`, `buffering` or `seeking`. The video element behaves differently across browsers, browser versions, and player engines. Events are often emitted in an unpredictable manner, regardless of what the specification says. The media-event-filter wraps these differences, and provides an API that is easy to consume.\n\nAdheres to the [Eyevinn Player Analytics Specification](https://github.com/Eyevinn/player-analytics-specification).\n\n## Overview\n\n```typescript\n/** Loading of stream is complete, playback is ready to start */\n/** It is now safe to let the user press a play button */\n\"loaded\";\n/** A seek has started */\n\"seeking\";\n/** A seek has ended  */\n\"seeked\";\n/** Buffering has started */\n/** Does not trigger during seek, cancelled by a seek */\n\"buffering\";\n/** Buffering has ended */\n/** Does not trigger during seek */\n\"buffered\";\n/** A request to start playing again has been made */\n\"play\";\n/** The stream has started playing after loading completed\n *  OR the stream has started playing after the stream was previously paused */\n\"playing\";\n/** The stream has been paused */\n\"pause\";\n/** The end of the stream was reached */\n\"ended\";\n/** A timeupdate event */\n\"timeupdate\";\n```\n\n## Limitations\n\nDoes not support native HTML5 MSE controls (`\u003cvideo controls\u003e`). It works, but event sequence will not strictly follow EPAS in all browsers.\n\n## Usage\n\n`npm install @eyevinn/media-event-filter`\n\n`yarn add @eyevinn/media-event-filter`\n\nExample of creating and listening to the event filter.\n\n```typescript\nimport {\n  getMediaEventFilter,\n  FilteredMediaEvent,\n} from \"@eyevinn/media-event-filter\";\n\nconst videoElement = document.createElement(\"video\");\n\n// Using a switch statement\n\nconst mediaEventFilter = getMediaEventFilter({\n  mediaElement: videoElement,\n  callback: (event: FilteredMediaEvent) =\u003e {\n    switch (event) {\n      case FilteredMediaEvent.LOADED:\n        // handle loaded\n        break;\n      case FilteredMediaEvent.BUFFERING:\n        // handle buffering\n        break;\n      case FilteredMediaEvent.BUFFERED:\n      // handle buffered\n      // ...\n      default:\n        break;\n    }\n  },\n});\n\n// Call when done\nmediaEventFilter.teardown();\n```\n\n```typescript\n// Object notation can also be used\n\nconst handlers = {\n  [FilteredMediaEvent.LOADED]: () =\u003e {\n    /* handle loaded */\n  },\n  [FilteredMediaEvent.BUFFERING]: () =\u003e {\n    /* handle buffering */\n  },\n  [FilteredMediaEvent.BUFFERED]: () =\u003e {\n    /* handle buffered */\n  },\n  // ...\n};\n\nconst mediaEventFilter = getMediaEventFilter({\n  mediaElement: videoElement,\n  callback: (event: FilteredMediaEvent) =\u003e handlers[event]?.(),\n});\n```\n\n```typescript\n// It is safe to use destructuring\n\nconst { teardown } = getMediaEventFilter({\n  /* ... */\n});\n\nteardown();\n```\n\n### Sample Shaka + React Component\n\nThe filter can be used to easily build a React UI on top of Shaka.\n\nA barebones sample integration (see it on [codepen](https://codepen.io/atlimar/pen/wvEmpXM?editors=1010)):\n\n```javascript\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"React\";\nimport shaka from \"shaka-player\";\nimport {\n  FilteredMediaEvent,\n  getMediaEventFilter,\n} from \"@eyevinn/media-event-filter\";\n\nconst PlayerComponent = ({ videoUrl }) =\u003e {\n  const videoRef = useRef(null);\n  const [playing, setPlaying] = useState(false);\n  const [loading, setLoading] = useState(true);\n  const [blocked, setBlocked] = useState(false);\n\n  useEffect(() =\u003e {\n    if (!videoUrl || !videoRef.current) return () =\u003e {};\n\n    const eventFilter = getMediaEventFilter({\n      mediaElement: videoRef.current,\n      // add your state handlers here\n      callback: (event) =\u003e {\n        switch (event) {\n          case FilteredMediaEvent.LOADED:\n            setLoading(false);\n\n            // attempt autoplay\n            videoRef.current.play().catch((e) =\u003e {\n              // catch autoplay block\n              if (e.name.indexOf(\"NotAllowedError\") \u003e -1) {\n                setBlocked(true);\n              }\n            });\n            break;\n          case FilteredMediaEvent.PLAYING:\n            // reset autplay blocked\n            setBlocked(false);\n            // we're playing!\n            setPlaying(true);\n            break;\n          case FilteredMediaEvent.ENDED:\n          case FilteredMediaEvent.PAUSE:\n            setPlaying(false);\n            break;\n          default:\n            break;\n        }\n      },\n    });\n\n    const player = new shaka.Player(videoRef.current);\n\n    // Add configuration if needed\n    // player.configure()\n\n    player\n      // start loading the stream\n      .load(videoUrl)\n      // catch errors during load\n      .catch(console.error);\n\n    // Kill player when unmounted\n    return () =\u003e {\n      player.destroy();\n      eventFilter.teardown();\n    };\n  }, [videoUrl, videoRef]);\n\n  const play = useCallback(() =\u003e {\n    if (!videoRef.current) return;\n\n    videoRef.current.play();\n  }, [videoRef]);\n\n  const pause = useCallback(() =\u003e {\n    if (!videoRef.current) return;\n\n    videoRef.current.pause();\n  }, [videoRef]);\n\n  return (\n    \u003cdiv style={{ width: \"720px\", margin: \"20px auto\" }}\u003e\n      {loading \u0026\u0026 \u003cp\u003eVideo is Loading\u003c/p\u003e}\n\n      {!loading \u0026\u0026\n        (playing ? (\n          \u003cbutton type=\"button\" onClick={pause}\u003e\n            Pause\n          \u003c/button\u003e\n        ) : (\n          \u003cbutton type=\"button\" onClick={play}\u003e\n            Play\n          \u003c/button\u003e\n        ))}\n\n      {blocked \u0026\u0026 \u003cp\u003eAutoplay blocked, please start playback manually\u003c/p\u003e}\n\n      \u003cvideo ref={videoRef} style={{ width: \"100%\", height: \"auto\" }} /\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\n```javascript\n// Use it:\n\u003cPlayerComponent videoUrl=\"https://bitmovin-a.akamaihd.net/content/MI201109210084_1/mpds/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.mpd\" /\u003e\n```\n\n## Benefits\n\nGet a single source of truth for playback events regardless of engine (Shaka, Hls.js, DashJS, native) or browser (Chrome, Firefox, Safari).\n\nPipe the events directly into your UI state management for a reliable source of truth of playback state updates.\n\nThe event sequence map directly to popular tracking providers and playback SDKs without further filtering, like Youbora, Comscore, Yospace, or Nielsen. _If yours is also supported we welcome PRs to update this list!_\n\nCompatible with [EPAS](https://github.com/Eyevinn/player-analytics-specification).\n\n## Events\n\nA description of events and their sequencing.\n\n### Example Sequences\n\n```typescript\nLOADED; // playback is ready to start\nPLAYING; // playback started\nSEEKING; // seek requested\nPAUSED; // manual pause\nPLAY; // manual play\nSEEKED; // seek finished\nPLAYING; // video is rolling again\nBUFFERING; // unable to continue playing due to missing buffer\nBUFFERED; // buffer ended by incoming seek request\nSEEKING; // seek requested\nSEEKED; // seek finished\nPAUSED; // manual pause\nPLAY; // manual play\nPLAYING; // video is rolling again\nENDED; // video reached the end\n```\n\n### loaded\n\nThe initial load of the video has completed, and it is ready to start playing.\n\nNo other event can trigger before loaded.\n\n### seeking\n\nSeeking has started.\n\nIf buffering is ongoing buffered will be triggered prior to seeking.\n\nBuffer events can not trigger during a seek.\n\nCan not trigger before loaded.\n\n### seeked\n\nSeeking has ended.\n\nCan not trigger before loaded.\n\nCan not trigger without a preceding seeking event.\n\n### buffering\n\nBuffering has started.\n\nCan not trigger during a seek.\n\nCan not trigger before loaded.\n\n### buffered\n\nBuffering has ended, or was interrupted by a seek.\n\nCan not trigger during a seek.\n\nCan not trigger without a preceding buffering event.\n\n### play\n\nPlayback has been requested.\n\nCan not trigger before loaded.\n\nCan not trigger if video was not previously paused.\n\n### playing\n\nPlayback has started.\n\nCan not trigger before loaded.\n\nCan not trigger during seeking.\n\nCan not trigger during buffering.\n\nCan not trigger if video was not previously paused.\n\nA play requested during seeking or buffering will trigger playing after the seek or buffer has finished.\n\n### pause\n\nPlayback has been paused.\n\nSeeking or buffering do not count as pausing.\n\nCan not trigger before loaded.\n\n### timeupdate\n\nA timeupdate event\n\nCan not trigger before loaded.\n\n### ended\n\nThe player has reached the end of a stream.\n\n## Contributing\n\nContributions to improve compatibility with or add support for different engines, tracking solutions, and browsers are welcome.\n\n### Git Ways of Working\n\nThe project uses feature branches, and a [rebase merge strategy](https://www.atlassian.com/git/tutorials/merging-vs-rebasing).\n\nMake sure you have `git pull` set to rebase mode:\n\n`git config pull.rebase true`\n\nOptionally, you can add the `--global` flag to the above command.\n\nTo start working on a new feature: `git checkout \u003cfeature branch name\u003e`.\n\nAs the project uses semantic-release to **automatically generate release notes** based on commits, it is important to follow some rules when committing.\n\nThis project uses [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary).\n\nRead [Using Git with Discipline](https://drewdevault.com/2019/02/25/Using-git-with-discipline.html).\n\nRead [How to Write a Commit Message](https://chris.beams.io/posts/git-commit/).\n\nA commit should:\n\n- contain a single change set (smaller commits are better)\n- pass tests, linting, and typescript checks\n- not be broken\n\nAlong with enabling time saving automation, it enables extremely powerful debug workflows via [git bisect](https://git-scm.com/docs/git-bisect), making bug hunting a matter of minutes instead of days. There are a number of articles out there on the magic of bisecting.\n\nBasic structure of a commit message:\n\n```\n\u003ctype\u003e[optional scope]: \u003ctitle starting with verb in infinitive\u003e\n\n[optional body]\n\n[optional footer]\n```\n\nFor automated release notes to work well, try to describe what was added or changed, instead of describing what the code does. Example:\n\n`fix(seek): rewrite calculation in seek module` `// bad, the consumer does not know what issue this fixes`\n\n`fix(seek): stop player from freezing after seek` `// good, the consumer understands what is now working again`\n\n### Develop\n\nTo start a dev server: `pnpm dev`, check `demo` and `public/index.html` for details.\n\nFamiliarity with the HTML5 video standard, shaka, hlsjs, or other engines\nis recommended before contributing.\n\nhttps://www.w3.org/TR/2011/WD-html5-20110113/video.html\n\nhttps://html.spec.whatwg.org/multipage/media.html\n\nhttps://html.spec.whatwg.org/multipage/media.html#mediaevents\n\nTested with shaka 2.5.X - 4.X.X\n\nTested with native video in Safari\n\nTested with hls.js\n\nTested on Safari 13.1+, Firefox, Chrome, Edge\n\nTested on OSX, Windows, Linux\n\n### Releasing\n\nReleases are triggered via a github action that will automatically increment the version and write a changelog based on commits.\n\nManual releases can be made by running `pnpm release`.\n\n# Support\n\nJoin our [community on Slack](http://slack.streamingtech.se) where you can post any questions regarding any of our open source projects. Eyevinn's consulting business can also offer you:\n\n- Further development of this component\n- Customization and integration of this component into your platform\n- Support and maintenance agreement\n\nContact [sales@eyevinn.se](mailto:sales@eyevinn.se) if you are interested.\n\n# About Eyevinn Technology\n\n[Eyevinn Technology](https://www.eyevinntechnology.se) is an independent consultant firm specialized in video and streaming. Independent in a way that we are not commercially tied to any platform or technology vendor. As our way to innovate and push the industry forward we develop proof-of-concepts and tools. The things we learn and the code we write we share with the industry in [blogs](https://dev.to/video) and by open sourcing the code we have written.\n\nWant to know more about Eyevinn and how it is to work here. Contact us at work@eyevinn.se!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feyevinn%2Fmedia-event-filter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feyevinn%2Fmedia-event-filter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feyevinn%2Fmedia-event-filter/lists"}