{"id":7114,"date":"2026-09-09T10:22:20","date_gmt":"2026-09-09T08:22:20","guid":{"rendered":"https:\/\/www.pschatzmann.ch\/home\/?p=7114"},"modified":"2026-09-09T10:38:46","modified_gmt":"2026-09-09T08:38:46","slug":"audiotools-the-videoplayer","status":"publish","type":"post","link":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/","title":{"rendered":"AudioTools: The VideoPlayer"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><em>A follow-up to <a href=\"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/04\/audiotools-videos\/\">AudioTools video support<\/a>, which covers container\/codec selection (AVI vs. MP4 vs. MPG, MJPEG vs. H.264). This post is the other half: the pipeline that actually plays a file, a minimal sketch, and every class involved.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why this exists<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">On a desktop, &#8220;play this video&#8221; hands the problem to an OS: a media framework demuxes the container, decodes on a scheduler-managed thread, and a compositor times frames to the display&#8217;s refresh. An ESP32 has none of that. Reading the container, decoding a frame, and pushing pixels to a panel are three jobs competing for the same loop, and if you decode as fast as the CPU allows, frames arrive whenever they&#8217;re ready \u2014 not when the video&#8217;s own frame rate says they should.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>VideoPlayer<\/code> (<code>AudioTools\/Video\/VideoPlayer.h<\/code>) is the video counterpart of this library&#8217;s <code>AudioPlayer<\/code>: one object that owns a container demuxer, a codec decoder, a background-task frame queue that paces output to real time, and \u2014 optionally \u2014 an audio decode chain kept in sync against it. Nothing is registered by default; you wire in exactly the container and codec your content uses, which keeps a sketch that only plays Motion-JPEG AVI files from pulling in an H.264 decoder it will never call.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The pipeline<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">One <code>copy()<\/code> call per <code>loop()<\/code> iteration reads a chunk from your source and feeds it through the demuxer, which splits it into a video track and an optional audio track. Video is queued and paced by a dedicated task; audio, if present, becomes the clock the video schedule is checked against.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Stream&amp;  ---&gt;  Demuxer\n                 \u2502\n        \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n        \u2502                  \u2502\n   video track         audio track (optional)\n        \u2502                  \u2502\nPacedVideoOutput      AudioDecoder (MP3\/AAC\/WAV)\n   (queue + task)           \u2502\n        \u2502            AudioTimeSourceStream (the clock)\n   VideoDecoder              \u2502\n (H264\/MJPEG\/MPG)      AudioOutput (I2S\/board)\n        \u2502\n   VideoOutput\n  (panel\/GPU)\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The audio branch doesn&#8217;t just play sound \u2014 by default it becomes the clock the video branch schedules against, so video stays locked to audio even when the two decode at slightly different rates. Drop the audio output entirely and <code>VideoPlayer<\/code> falls back to a free-running timer, which is all a silent or video-only file needs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Minimal example<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Video-only playback of a Motion-JPEG AVI from an SD card to a TinyGPU-driven panel, no audio track involved. This is close to the smallest sketch that plays a real file: one demuxer, one decoder, one output, wired through the two-argument <code>VideoPlayer<\/code> constructor.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">#include \"AudioTools.h\"<\/span>\n<span class=\"hljs-comment\">#include \"AudioTools\/AudioCodecs\/ContainerAVI.h\"<\/span>\n<span class=\"hljs-comment\">#include \"AudioTools\/Video\/CodecJPEG.h\"<\/span>\n<span class=\"hljs-comment\">#include \"AudioTools\/Video\/OutputTinyGPU.h\"<\/span>\n<span class=\"hljs-comment\">#include \"AudioTools\/Video\/VideoPlayer.h\"<\/span>\n<span class=\"hljs-comment\">#include \"TinyGPU\/Boards.h\"<\/span>\n<span class=\"hljs-comment\">#include &lt;SD_MMC.h&gt;<\/span>\n\n<span class=\"hljs-comment\">\/\/ container, panel, and the player that wires them together<\/span>\nDemuxerAVI aviDemuxer;\nLCDBoardESP32S3_2_8Display board;\nOutputTinyGPU tftOutput(board);\nVideoPlayer player(aviDemuxer, tftOutput);   <span class=\"hljs-comment\">\/\/ video only: no audio arg<\/span>\nMJPEGDecoder mjpegDecoder;\nFile file;\n\n<span class=\"hljs-keyword\">const<\/span> char* path = <span class=\"hljs-string\">\"\/Videos\/clip176x144-mjpeg.avi\"<\/span>;\n\nvoid setup() {\n  Serial.begin(<span class=\"hljs-number\">115200<\/span>);\n  SD_MMC.begin();\n  board.begin();\n  tftOutput.setScaleToFit(<span class=\"hljs-keyword\">true<\/span>);   <span class=\"hljs-comment\">\/\/ upscale to fill the panel<\/span>\n\n  player.addVideoDecoder(mjpegDecoder);  <span class=\"hljs-comment\">\/\/ registered by codec fourcc<\/span>\n\n  file = SD_MMC.open(path);\n  <span class=\"hljs-keyword\">if<\/span> (!file || !player.begin(file)) {\n    Serial.println(<span class=\"hljs-string\">\"could not start playback\"<\/span>);\n    <span class=\"hljs-keyword\">while<\/span> (<span class=\"hljs-keyword\">true<\/span>) delay(<span class=\"hljs-number\">1000<\/span>);\n  }\n}\n\nvoid loop() {\n  <span class=\"hljs-keyword\">if<\/span> (player.copy() == <span class=\"hljs-number\">0<\/span>) {      <span class=\"hljs-comment\">\/\/ 0 == source exhausted<\/span>\n    file.close();\n    <span class=\"hljs-keyword\">while<\/span> (<span class=\"hljs-keyword\">true<\/span>) delay(<span class=\"hljs-number\">1000<\/span>);\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><strong>Adding audio:<\/strong> pass a third argument \u2014 an <code>AudioOutput<\/code>, <code>AudioStream<\/code>, or plain <code>Print<\/code> \u2014 to the <code>VideoPlayer<\/code> constructor, register an audio decoder with <code>addAudioDecoder(decoder, mime)<\/code>, and playback locks video to that output&#8217;s own audio clock automatically. Call <code>setUseAudioClock(false)<\/code> only if the track never actually delivers bytes (a silent or empty stream would otherwise stall the video schedule forever).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Classes and dependencies<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every piece below lives under <code>AudioTools\/Video\/<\/code> or <code>AudioTools\/AudioCodecs\/<\/code> and is opt-in \u2014 <code>VideoPlayer<\/code> starts empty; <code>VideoPlayerFull<\/code> pre-registers the whole set at the cost of linking every dependency at once.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Pipeline core<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Class<\/th><th>Role<\/th><th>Header<\/th><th>External dependency<\/th><\/tr><\/thead><tbody><tr><td><code>VideoPlayer<\/code><\/td><td>Owns demuxer + decoder + sync task; one <code>copy()<\/code> per loop<\/td><td><code>Video\/VideoPlayer.h<\/code><\/td><td>none<\/td><\/tr><tr><td><code>VideoPlayerFull<\/code><\/td><td><code>VideoPlayer<\/code> with every demuxer\/codec pre-registered<\/td><td><code>Video\/VideoPlayerFull.h<\/code><\/td><td>everything below, unconditionally<\/td><\/tr><tr><td><code>PacedVideoOutput<\/code><\/td><td>Frame queue + background task that paces decode to real time, drops\/resyncs on backlog<\/td><td><code>Video\/PacedVideoOutput.h<\/code><\/td><td>FreeRTOS task\/queue<\/td><\/tr><tr><td><code>AudioTimeSourceStream<\/code><\/td><td>Wraps the audio output as the clock video is scheduled against<\/td><td><code>CoreAudio\/AudioIO.h<\/code><\/td><td>none<\/td><\/tr><tr><td><code>EncodedAudioStream<\/code><\/td><td>Audio decode chain feeding the real audio output<\/td><td><code>AudioCodecs\/AudioEncoded.h<\/code><\/td><td>none<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Containers (demux)<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Class<\/th><th>Role<\/th><th>Header<\/th><th>External dependency<\/th><\/tr><\/thead><tbody><tr><td><code>Demuxer<\/code> <em>(interface)<\/em><\/td><td>Container parsing contract; exposes video\/audio tracks + mime<\/td><td><code>AudioCodecs\/ContainerCommon.h<\/code><\/td><td>none<\/td><\/tr><tr><td><code>DemuxerAVI<\/code> \/ <code>MuxerAVI<\/code><\/td><td>AVI (RIFF) container \u2014 audio codec may be PCM\/MP3\/AAC<\/td><td><code>AudioCodecs\/ContainerAVI.h<\/code><\/td><td>none<\/td><\/tr><tr><td><code>DemuxerMP4<\/code> \/ <code>MuxerMP4<\/code><\/td><td>MP4\/MOV (ISO BMFF) container<\/td><td><code>AudioCodecs\/ContainerMP4.h<\/code><\/td><td>none<\/td><\/tr><tr><td><code>DemuxerMPG<\/code><\/td><td>MPEG program stream container<\/td><td><code>AudioCodecs\/ContainerMPG.h<\/code><\/td><td>none<\/td><\/tr><tr><td><code>MultiVideoDemuxer<\/code><\/td><td>Auto-selects a registered demuxer by mime type<\/td><td><code>Video\/MultiVideoDemuxer.h<\/code><\/td><td>none<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Video codecs (decode)<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Class<\/th><th>Role<\/th><th>Header<\/th><th>External dependency<\/th><\/tr><\/thead><tbody><tr><td><code>VideoDecoder<\/code> <em>(interface)<\/em><\/td><td>Decode contract; extends <code>VideoOutput<\/code> so decoders chain directly to a display<\/td><td><code>Video\/CodecVideo.h<\/code><\/td><td>none<\/td><\/tr><tr><td><code>MJPEGDecoder<\/code><\/td><td>Motion-JPEG, every frame independently decodable<\/td><td><code>Video\/CodecJPEG.h<\/code><\/td><td>TinyJPEG (<code>pschatzmann\/TinyJPEG<\/code>)<\/td><\/tr><tr><td><code>H264Decoder<\/code><\/td><td>Software H.264 decode<\/td><td><code>Video\/CodecH264.h<\/code><\/td><td>external H.264 software decoder<\/td><\/tr><tr><td><code>H264DecoderESP32S3<\/code><\/td><td>Hardware-assisted H.264 decode on ESP32-S3<\/td><td><code>Video\/CodecH264ESP32S3.h<\/code><\/td><td>ESP32-S3 HW codec (ESP-IDF)<\/td><\/tr><tr><td><code>H264EncoderESP32S3<\/code> \/ <code>H264EncoderESP32P4<\/code><\/td><td>Hardware H.264 encode (camera \u2192 stream use cases)<\/td><td><code>Video\/CodecH264ESP32{S3,P4}.h<\/code><\/td><td>ESP32-S3\/P4 HW codec (ESP-IDF)<\/td><\/tr><tr><td><code>MPGDecoder<\/code> \/ <code>MPGEncoder<\/code><\/td><td>MPEG-1 video decode\/encode<\/td><td><code>Video\/CodecMPG.h<\/code><\/td><td>external MPEG-1 codec<\/td><\/tr><tr><td><code>MultiVideoDecoder<\/code><\/td><td>Auto-selects a registered decoder by codec fourcc<\/td><td><code>Video\/MultiVideoDecoder.h<\/code><\/td><td>none<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Display outputs<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Class<\/th><th>Role<\/th><th>Header<\/th><th>External dependency<\/th><\/tr><\/thead><tbody><tr><td><code>VideoOutput<\/code> <em>(interface)<\/em><\/td><td>Display-sink contract every decoder\/output implements<\/td><td><code>Video\/VideoOutput.h<\/code><\/td><td>none<\/td><\/tr><tr><td><code>OutputTinyGPU<\/code><\/td><td>GPU-accelerated scale\/blit to a supported panel<\/td><td><code>Video\/OutputTinyGPU.h<\/code><\/td><td>TinyGPU (<code>pschatzmann\/TinyGPU<\/code>)<\/td><\/tr><tr><td><code>OutputTFT_eSPI<\/code><\/td><td>Renders through the TFT_eSPI panel driver<\/td><td><code>Video\/OutputTFT_eSPI.h<\/code><\/td><td>Bodmer\/TFT_eSPI<\/td><\/tr><tr><td><code>OutputOpenCV<\/code><\/td><td>Preview window for desktop\/simulator builds<\/td><td><code>Video\/OutputOpenCV.h<\/code><\/td><td>OpenCV<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Audio side (optional track)<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Class<\/th><th>Role<\/th><th>Header<\/th><th>External dependency<\/th><\/tr><\/thead><tbody><tr><td><code>MultiDecoder<\/code><\/td><td>Auto-selects a registered audio decoder by mime<\/td><td><code>AudioCodecs\/MultiDecoder.h<\/code><\/td><td>none<\/td><\/tr><tr><td><code>MP3DecoderHelix<\/code> \/ <code>AACDecoderHelix<\/code> \/ <code>WAVDecoder<\/code><\/td><td>Common AVI\/MP4 audio tracks<\/td><td><code>AudioCodecs\/CodecMP3Helix.h<\/code> etc.<\/td><td>libhelix (bundled)<\/td><\/tr><tr><td><code>AudioBoardStream<\/code> \/ <code>I2SStream<\/code><\/td><td>Final audio sink (speaker\/DAC)<\/td><td><code>AudioLibs<\/code> \/ <code>CoreAudio<\/code><\/td><td>arduino-audio-driver (board-dependent)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Tuning knobs worth knowing about<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>PacedVideoOutput<\/code> does the actual scheduling, and <code>VideoPlayer<\/code> forwards its controls: <code>setSchedulingDelayMs()<\/code> compensates for your audio output&#8217;s own buffering latency (audio &#8220;plays&#8221; the instant it&#8217;s written, not when the DAC emits it), <code>setTaskParameters()<\/code> pins the render task to its own CPU core so a slow frame never stalls the loop doing SD reads and demuxing, and <code>setResyncThresholdMs()<\/code> \/ <code>setMaxQueuedIFrames()<\/code> decide how playback recovers once it falls behind \u2014 dropping frames slows a backlog&#8217;s growth, but only a forward jump in the schedule actually clears one.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Transcoding Videos<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Please note that none of this plays raw camera footage \u2014 everything needs to be downscaled and re-encoded first. `176\u00d7144` at 7\u20138 fps is the practical baseline; going higher roughly triples decode+render cost per frame.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In additioin, PSRAM is required, not optional \u2014 decoded-picture buffers and the frame queue don&#8217;t fit in internal SRAM. Full ffmpeg variants and measured fps per codec can be found in the <a href=\"https:\/\/github.com\/pschatzmann\/arduino-audio-tools\/wiki\/Video-Playback\">Video Playback wiki <\/a>page.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><em>AudioTools\/Video \u00b7 <a href=\"https:\/\/github.com\/pschatzmann\/arduino-audio-tools\">github.com\/pschatzmann\/arduino-audio-tools<\/a><\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A follow-up to AudioTools video support, which covers container\/codec selection (AVI vs. MP4 vs. MPG, MJPEG vs. H.264). This post is the other half: the pipeline that actually plays a file, a minimal sketch, and every class involved. Why this exists On a desktop, &#8220;play this video&#8221; hands the problem [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":7102,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_import_markdown_pro_load_document_selector":0,"_import_markdown_pro_submit_text_textarea":"","_exactmetrics_skip_tracking":false,"footnotes":""},"categories":[20],"tags":[67],"class_list":["post-7114","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-arduino","tag-video"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>AudioTools: The VideoPlayer - Phil Schatzmann<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"AudioTools: The VideoPlayer - Phil Schatzmann\" \/>\n<meta property=\"og:description\" content=\"A follow-up to AudioTools video support, which covers container\/codec selection (AVI vs. MP4 vs. MPG, MJPEG vs. H.264). This post is the other half: the pipeline that actually plays a file, a minimal sketch, and every class involved. Why this exists On a desktop, &#8220;play this video&#8221; hands the problem [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/\" \/>\n<meta property=\"og:site_name\" content=\"Phil Schatzmann\" \/>\n<meta property=\"article:published_time\" content=\"2026-09-09T08:22:20+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-09T08:38:46+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2026\/09\/movie-player.jpeg\" \/>\n\t<meta property=\"og:image:width\" content=\"599\" \/>\n\t<meta property=\"og:image:height\" content=\"511\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"pschatzmann\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"pschatzmann\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"4 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/\"},\"author\":{\"name\":\"pschatzmann\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#\\\/schema\\\/person\\\/73a53638a4e34e8373405fd737dac9b1\"},\"headline\":\"AudioTools: The VideoPlayer\",\"datePublished\":\"2026-09-09T08:22:20+00:00\",\"dateModified\":\"2026-09-09T08:38:46+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/\"},\"wordCount\":846,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#\\\/schema\\\/person\\\/73a53638a4e34e8373405fd737dac9b1\"},\"image\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/movie-player.jpeg\",\"keywords\":[\"Video\"],\"articleSection\":[\"Arduino\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/\",\"url\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/\",\"name\":\"AudioTools: The VideoPlayer - Phil Schatzmann\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/movie-player.jpeg\",\"datePublished\":\"2026-09-09T08:22:20+00:00\",\"dateModified\":\"2026-09-09T08:38:46+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/movie-player.jpeg\",\"contentUrl\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/movie-player.jpeg\",\"width\":599,\"height\":511},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/09\\\/09\\\/audiotools-the-videoplayer\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"AudioTools: The VideoPlayer\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#website\",\"url\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/\",\"name\":\"Phil Schatzmann Consulting\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#\\\/schema\\\/person\\\/73a53638a4e34e8373405fd737dac9b1\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#\\\/schema\\\/person\\\/73a53638a4e34e8373405fd737dac9b1\",\"name\":\"pschatzmann\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/pschatzmann.png\",\"url\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/pschatzmann.png\",\"contentUrl\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/pschatzmann.png\",\"width\":305,\"height\":305,\"caption\":\"pschatzmann\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/pschatzmann.png\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"AudioTools: The VideoPlayer - Phil Schatzmann","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/","og_locale":"en_US","og_type":"article","og_title":"AudioTools: The VideoPlayer - Phil Schatzmann","og_description":"A follow-up to AudioTools video support, which covers container\/codec selection (AVI vs. MP4 vs. MPG, MJPEG vs. H.264). This post is the other half: the pipeline that actually plays a file, a minimal sketch, and every class involved. Why this exists On a desktop, &#8220;play this video&#8221; hands the problem [&hellip;]","og_url":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/","og_site_name":"Phil Schatzmann","article_published_time":"2026-09-09T08:22:20+00:00","article_modified_time":"2026-09-09T08:38:46+00:00","og_image":[{"width":599,"height":511,"url":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2026\/09\/movie-player.jpeg","type":"image\/jpeg"}],"author":"pschatzmann","twitter_card":"summary_large_image","twitter_misc":{"Written by":"pschatzmann","Est. reading time":"4 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/#article","isPartOf":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/"},"author":{"name":"pschatzmann","@id":"https:\/\/www.pschatzmann.ch\/home\/#\/schema\/person\/73a53638a4e34e8373405fd737dac9b1"},"headline":"AudioTools: The VideoPlayer","datePublished":"2026-09-09T08:22:20+00:00","dateModified":"2026-09-09T08:38:46+00:00","mainEntityOfPage":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/"},"wordCount":846,"commentCount":0,"publisher":{"@id":"https:\/\/www.pschatzmann.ch\/home\/#\/schema\/person\/73a53638a4e34e8373405fd737dac9b1"},"image":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/#primaryimage"},"thumbnailUrl":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2026\/09\/movie-player.jpeg","keywords":["Video"],"articleSection":["Arduino"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/","url":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/","name":"AudioTools: The VideoPlayer - Phil Schatzmann","isPartOf":{"@id":"https:\/\/www.pschatzmann.ch\/home\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/#primaryimage"},"image":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/#primaryimage"},"thumbnailUrl":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2026\/09\/movie-player.jpeg","datePublished":"2026-09-09T08:22:20+00:00","dateModified":"2026-09-09T08:38:46+00:00","breadcrumb":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/#primaryimage","url":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2026\/09\/movie-player.jpeg","contentUrl":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2026\/09\/movie-player.jpeg","width":599,"height":511},{"@type":"BreadcrumbList","@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/09\/09\/audiotools-the-videoplayer\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.pschatzmann.ch\/home\/"},{"@type":"ListItem","position":2,"name":"AudioTools: The VideoPlayer"}]},{"@type":"WebSite","@id":"https:\/\/www.pschatzmann.ch\/home\/#website","url":"https:\/\/www.pschatzmann.ch\/home\/","name":"Phil Schatzmann Consulting","description":"","publisher":{"@id":"https:\/\/www.pschatzmann.ch\/home\/#\/schema\/person\/73a53638a4e34e8373405fd737dac9b1"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.pschatzmann.ch\/home\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.pschatzmann.ch\/home\/#\/schema\/person\/73a53638a4e34e8373405fd737dac9b1","name":"pschatzmann","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2022\/08\/pschatzmann.png","url":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2022\/08\/pschatzmann.png","contentUrl":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2022\/08\/pschatzmann.png","width":305,"height":305,"caption":"pschatzmann"},"logo":{"@id":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2022\/08\/pschatzmann.png"}}]}},"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/posts\/7114","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/comments?post=7114"}],"version-history":[{"count":5,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/posts\/7114\/revisions"}],"predecessor-version":[{"id":7121,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/posts\/7114\/revisions\/7121"}],"wp:attachment":[{"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/media?parent=7114"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/categories?post=7114"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/tags?post=7114"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}