Skip to content

KitePlayer 0.2.0

Choose a tag to compare

@yuroyami yuroyami released this 29 Sep 23:52

Closing 180 issues.

Usage

// Native views, no Compose: the engine, FFmpeg, HTTP and HTTPS, libass subtitles and the input doors
implementation("io.github.yuroyami:kiteplayer:0.2.0")

// Compose: everything above, plus both Compose video paths
implementation("io.github.yuroyami:kiteplayer-compose:0.2.0")

// Optional, next to either line
implementation("io.github.yuroyami:kiteplayer-audioviz:0.2.0")      // an audio visualiser for files with no picture, in Compose

// Or build your own player from the parts
implementation("io.github.yuroyami:kiteplayer-core:0.2.0")          // the engine and its service interfaces, with no FFmpeg, Ktor or Compose
implementation("io.github.yuroyami:kiteplayer-ffmpeg:0.2.0")        // the FFmpeg backend (pulls KiteFFmpeg)
implementation("io.github.yuroyami:kiteplayer-output:0.2.0")        // audio sinks and video renderers for each platform
implementation("io.github.yuroyami:kiteplayer-network:0.2.0")       // HTTP and HTTPS through Ktor
implementation("io.github.yuroyami:kiteplayer-io:0.2.0")            // input doors for files, streams, content URIs and assets
implementation("io.github.yuroyami:kiteplayer-libass:0.2.0")        // libass typesetting for ASS and SSA subtitles
implementation("io.github.yuroyami:kiteplayer-view:0.2.0")          // native video views
implementation("io.github.yuroyami:kiteplayer-compose-ui:0.2.0")    // both Compose video paths, for a player you build yourself

kiteplayer-rt, kiteplayer-subtitles, kiteplayer-view-bindings, kiteplayer-compose-video, kiteplayer-compose-interop and kiteplayer-phone arrive with the lines above. Do not add them yourself.

Changelog

🔴 Breaking changes

Change What to do
#75: PlaybackWarning has a new GaplessFallback value Handle GaplessFallback in a when that has no else
#75: Queue items now follow each other without a gap, and the status stays Playing between items Set PlayerConfig.queue to QueueConfig(gapless = false) to keep the old path
#71: PlaybackWarning has a new DeinterlaceUnavailable value Handle DeinterlaceUnavailable in a when that has no else
#71: PlayerConfig.deinterlace is Auto by default, so an interlaced stream now decodes in software Pass DeinterlacePolicy.Off to keep hardware decoding
0e003dc: PlaybackWarning has a new SubtitlesNotDrawn value Handle SubtitlesNotDrawn in a when that has no else
0e003dc: The engine holds every subtitle rasterizer to the limits in the SubtitleRasterizer companion object, and cues past a limit are not drawn Throw SubtitleOverlayLimitException from a custom rasterizer to show part of an overlay
e54f920: AudioSinkEvent has a new Failed value Handle Failed in a when that has no else
#228: RendererEvent has a new ColorApproximated value Handle ColorApproximated in a when that has no else
#92: PlaybackError.AudioDeviceUnavailable now takes device and detail Pass both arguments
#97: KiteFFmpegVideoFrame.frame and AudioSamples are internal Draw web frames with KiteFFmpegWebPainter instead of WebRgbaConverter
#218: MasterClock.External, PlaybackError.DecoderUnavailable, OutputBackend.videoRenderer and AudioSinkBuffer.writePlane are removed Delete the when branches, and delete the overrides of the last two
#218: KitePlayer.create refuses SyncMode.ExternalMaster with ConfigurationInvalid Use another SyncMode
#220: AudioPlayback.submit is internal Call submitDecoded
#274: XmlMini and XmlElement are internal Use DashManifestParser.parse for a DASH manifest
#217: setSleepTimer throws IllegalArgumentException for a negative fade or an After that is not in the future Pass a valid fade and time
#256: openQueue refuses a session that is not Idle, Ended or Failed Call stop() first
#219: setAudioDelay, setSubtitleDelay, SubtitleConfig.delay and restore refuse a value that is not finite or is beyond KitePlayer.DELAY_MAX Keep delays within one hour either way
#203: ReplayGain no longer raises a track whose tags carry no peak, and AudioConfig.volumeCeiling no longer widens its clamp Raise the volume to make such a track louder
#280: InterruptionPolicy.duckVolume is now a factor on the listener's volume, not an absolute volume Set it as a share, for example 0.2 for a fifth
#214: PlayerMemento.FORMAT_VERSION is 4, and an older build refuses a memento that this one wrote Update every reader of stored mementos
#204: Every KitePlayer call that can fail declares @Throws, and KitePlayer.create throws a checked PlaybackException for Java Call them with try in Swift, and handle PlaybackException in Java
#240: Dash.mediaItemFor throws DashUnsupportedException for audio in its own adaptation set, live manifests and several Periods Catch it and fall back to another source
#237: On macOS the desktop JVM decodes with VideoToolbox under HwdecPolicy.Auto, and such frames report PlayerPixelFormat.Opaque Read planes through planeFormat, or pass HwdecPolicy.Off
#238: keepDisplayAwake is true by default, so the display no longer dims while video plays Pass keepDisplayAwake = false to manage the display yourself
#309: KitePlayerVideo has new trailing parameters, so a trailing lambda now binds to accessibilityStateFormat Pass onRendererAttached by name
264fbf6: The DASH door applies DashUrlPolicy to every redirect and follows at most five. Under DashUrlPolicy.SameOrigin a redirect to another scheme, host or port fails with DashUrlRefusedException Use DashUrlPolicy.Default for a CDN that redirects to another host
d2b315d: An external subtitle gets the item's headers only when it has the item's scheme, host and port Give its SubtitleSource its own io to send headers to another server

The rows about a default or a behaviour (the gapless queue, deinterlace, keepDisplayAwake, ReplayGain, ducking, redirects and subtitle headers) still compile. A by wrapper of VideoDecoderFactory that overrides only the old create also misses its new overload.

Everything since 0.0.27

AreaTypeChange
Playback (16)API Changes🔴 #217: setSleepTimer now throws IllegalArgumentException at the call for a bad fade or an After that is not in the future, and a refused A-B loop is published as CommandRefused.
🔴 #218: KitePlayer.create refuses SyncMode.ExternalMaster with ConfigurationInvalid. Removed MasterClock.External, PlaybackError.DecoderUnavailable, OutputBackend.videoRenderer and AudioSinkBuffer.writePlane, which nothing used.
#264: Thumbnail and Waveform now compare and hash their arrays by content.
Performance#245: Stopped a paused player from waking about 700 times a second.
Fixes#30: Made every read of the player's own doors interruptible and every wait bounded.
#31: Passed a cancel into FFmpeg's own protocols, so a stop or close ends an open that reads through the URL fallback or a file descriptor.
#200: Kept playing at the old position when a seek aborts because a worker could not park.
#202: Made inspect resolve readers the way open does, fail with a typed error and run off the caller's thread.
#213: Made restore check every setting before it changes anything, so a refused setting no longer leaves a half restored player.
🔴 #214: Kept item titles and the shuffle order in a PlayerMemento, and made fromProperties refuse a queue.size larger than its keys. PlayerMemento.FORMAT_VERSION is now 4.
#216: Stopped SleepTimer.After from counting paused time, and made SleepTimer.EndOfItem end a repeating item.
#226: Made the interruption and background guards pause or duck a buffering player, and one that opens the next queue item while playing. PlayerSnapshot gained playRequested.
#255: Left position() at its old value when a seek is refused or a seekLater is dropped.
#276: Let stop and close end a stream or Android pipe read that waits for bytes.
#277: Fixed a video track change at position zero that left the player discarding all output.
#278: Made a guard resume only a pause it made itself, by comparing the new KitePlayer.transportMark count.
Queue and gapless (4)New Features🔴 #75: Made queue items follow each other without a gap. The next item opens five seconds before the current one ends and plays on the same audio device, with QueueConfig(gapless = false) to keep the old path.
#304: Let a video queue on Android follow item to item without a gap in the sound.
API Changes🔴 #256: openQueue now refuses to replace a session that is not Idle, Ended or Failed, as open always did.
Performance#306: Played a preloaded item without opening it a second time after next() or a gapless fallback.
Audio (18)New Features🔴 #92: Added audioOutputDevices() and withAudioOutputDevice(id) to DesktopOutputBackend and AppleOutputBackend, to list output devices and bind a player to one.
#181: Added AudioConfig.upmix with UpmixMode.Surround, which plays a mono or stereo source from every speaker of a surround device.
#239: Added MediaItem.audioFilter, an FFmpeg audio filter chain that every decoded audio buffer runs through.
API Changes🔴 #220: Made AudioPlayback.submit internal, so submitDecoded is the one public feed and it runs every stage.
Performance2a0b364: Decoded audio buffers straight into the engine's interleaved array, which cuts the bytes allocated per buffer by half or more.
Fixes#199: Derived every downmix from the speakers with FFmpeg's rules, so layouts such as 5.0 side, 3.0, 4.0 and side quad keep their centre in a stereo downmix.
🔴 #203: Stopped ReplayGain from raising a track whose tags carry no peak, and stopped AudioConfig.volumeCeiling from widening its clamp.
#210: Matched FFmpeg's downmix gains for 6.1, 7.1 folds, mono and 2.1 targets.
#211: Made LoudnessMeter weight each channel by its speaker, so the LFE of 2.1 and 6.1 no longer counts as programme.
🔴 #219: Corrected the sign in the setAudioDelay KDoc, and refused an audio or subtitle delay that is not finite or is beyond KitePlayer.DELAY_MAX, one hour either way.
#221: Kept the Kotlin audio ring's device callback from waiting on the feeder on Android and the desktop JVM.
#222: Made the end-of-media drain stop at the engine's deadline on the desktop JVM and Android.
#257: Ran the equaliser, balance and ReplayGain over the audio tail at the end of a stream.
#260: Wrote 16-bit desktop audio with FFmpeg's scale and rounding, so unity-gain audio is bit-exact.
#261: Read the Android driver timestamp under one lock for both of its readers.
🔴 #280: Made a duck a multiplier on the listener's volume, so it can only make the sound quieter. KitePlayer.setDuckLevel is the new call, and InterruptionPolicy.duckVolume is now a factor.
#288: Applied the new track's ReplayGain on an in-place audio track switch.
#292: Signaled an audio seek landing only after the straddling buffer is trimmed.
Video and rendering (10)New Features🔴 #71: Deinterlaced interlaced video with FFmpeg's bwdif. PlayerConfig.deinterlace takes a DeinterlacePolicy, and Auto is the default.
Performance#247: Converted native software frames in four row slices, as the JVM does.
#8293638: Reused one RGBA array per renderer on the JVM and Android instead of allocating 8.3 MB for each 1080p frame.
Fixes#197: Made captureFrame return when the media has ended or a teardown interrupts it.
#198: Timed the frame after a late drop from the dropped frame's slot, so a drop no longer costs a whole extra frame period.
🔴 #228: Added RendererEvent.ColorApproximated, which the web canvas publishes for HDR shown without tone mapping and for YCgCo and FCC video.
#258: Converted FCC-tagged video with the FCC matrix instead of the BT.709 matrix.
#263: Closed the first decoded frame when a video filter graph cannot be built.
#293: Reported a native view attachment once per player, not on every recomposition.
#299: Made captureFrame and encode work while VideoToolbox decodes. SoftwareReadableFrame gained planeFormat.
Subtitles (14)New Features#98: Showed Blu-ray, DVB, DVD and XSUB image subtitles from the container.
29e6980: Played caption tracks stored on their own, such as a MOV c608 track, as selectable subtitle tracks.
Fixes#212: Kept an applied external subtitle, both selections and the typesetter across a video track switch and a hardware decoder recovery.
#215: Made the external subtitle loader wait when a reader answers that no bytes are ready yet, instead of reading it as the end of the file.
#223: Cleared the subtitle overlay only after the raster job it replaces, so a late job no longer leaves stale text.
#229: Anchored a WebVTT line percentage at the cue's top edge unless its line alignment names the centre or the end, and kept positioned cues inside the picture.
#230: Placed positioned subtitle cues on iOS and macOS by their alignment, as the other platforms do.
#231: Read SubRip {\anN} and other brace tags as ASS override tags instead of showing them as text.
#243: Refused a local subtitle file over the size limit before reading it, on the JVM and Android.
#287: Scaled bitmap subtitle regions in size as well as in position.
#291: Bounded what the web libass engine holds while its module loads.
🔴 0e003dc: Held every subtitle rasterizer to the limits in the SubtitleRasterizer companion object. Cues past a limit are not drawn and PlaybackWarning.SubtitlesNotDrawn says so, and playback continues when a rasterizer or renderer throws.
9719dd9: Moved the kiteplayer-libass chain to libass 0.17.5, with FriBidi 1.0.17, FreeType 2.14.3 and HarfBuzz 14.5.0, from KiteFFmpeg's ass-chain-r2 release. A typeset ASS line can shift by a pixel.
🔴 d2b315d: Sent the item's headers to an external subtitle only when it has the same scheme, host and port as the item's uri.
Visualiser (56)API Changes#297: Let rememberAudioVizState(player) compile without the drawing toolkit opt-in, because SongScanPolicy and SongMapStore lost @AudioVizAuthoringApi.
Performance#316: Closed the shader wrappers of older frames on desktop and iOS.
#331: Reused padded mesh arrays on desktop and iOS instead of allocating exact copies.
#332: Tested the Neon Lo-Fi floor cells without allocating a range for each cell on every frame.
#333: Read only the sampled pixels when making a palette from a cover.
#351: Built the Musical Spectrum note ruler once for one size instead of on every frame.
Fixes#227: Ignored a closed player when the visualiser leaves the screen.
#244: Created every missing directory level for the song map store on iOS.
#285: Kept scanning later songs after one song scan fails.
#286: Stepped both visualiser surfaces on one clock that follows the current director and analysis source.
#289: Applied the song scan policy before scanning starts and followed later changes to it.
#296: Gave the desktop and iOS samples every song in the shared media folder.
#298: Kept Honeycomb and Pipe within three flashes a second on a drum loop.
#311: Kept the waveform, the hits and the pause live under reduced motion.
#312: Gave each reader of the visualiser state its own event cursor, so a frame rate cap no longer drops events.
#313: Scaled the light of Bars and Alchemy by what the flash guard allows.
#314: Passed the reduced motion setting to the drawing browser tiles.
#315: Stopped the director from changing drawings with the strobe while reduced motion is on.
#317: Made the Neon Lo-Fi controls follow the sliders while the player is paused.
#318: Rebuilt the Iris background when the canvas width or height changes.
#321: Held every drawing still while the player is paused.
#322: Fixed how song map parts join, so a gap between two scan parts is no longer filled with the next part's level.
#323: Spaced key-change boundaries at least four seconds apart.
#324: Treated a stored song map with an unusable number as a cache miss.
#325: Showed the empty picture when the visualiser state gets a closed player.
#326: Gave the settings panel controls roles and values for a screen reader.
#327: Scaled the lane cut and the light speed of Pipe by reduced motion.
#330: Accepted a decoder block longer than a quarter of a second when the analysis queue is empty.
#334: Asked for linear filtering of the lookup pictures of Android runtime shaders.
#336: Restarted the song scan analysis when the timestamps jump, so events after a jump are no longer placed too early.
#337: Replaced a song map file with one move on desktop and Android.
#338: Checked the close and ordered files by nanoseconds in the iOS song map store.
#339: Adapted the level reference when the loudness meter cannot read the layout.
#342: Kept the calm reading gliding when the same frame comes back on a new step.
#343: Kept the cycle length and counted heard time through a pause.
#344: Fell back to the plain fade under reduced motion, and made the settings panel show the real choice.
#345: Scaled the ground and detail layers by what the flash guard allows.
#346: Rejected a stored song map with a value outside its range.
#349: Kept the surface clocks in Double, so they never stop advancing after long runs at high refresh rates.
#350: Scaled the floors of Thin Ice and Ocean Mist by the allowed light.
#352: Slowed the view moves and turns of Odyssey under reduced motion.
#353: Held the tear of the finishing pass while paused.
#354: Kept the glow of the finishing pass calm under reduced motion.
#355: Wrote the spectrum histories by heard time instead of by display step.
#356: Installed the events of a song map that holds more than 4096 of them, keeping the first 4096.
#357: Held every rise at the flash limit, so a slow fade can no longer open the flash guard.
#358: Cleared the palette turn of the ground and detail layers on reset.
#359: Faded a choice on from where the picture is when a second change lands.
#360: Kept the Thin Ice rings on their wells when the screen turns.
#361: Carried the tuned 60 Hz spring and envelope step over to any step length.
#362: Damped the snare shove and the drop fling of the flying camera under reduced motion.
#365: Filled the triangles of the mesh path before Android 10 with their mean colour, so a soft spot is no longer a solid disc.
#368: Removed two ShaderProgram functions that nothing called.
#369: Let the spring step reach heavy damping and steps longer than 50 ms.
#370: Turned the Thin Ice cracks with the rings when the screen turns.
#371: Started the tear of the finishing pass from valid strips.
Network and streaming (9)New Features🔴 #240: Made Dash.mediaItemFor refuse a manifest whose audio sits in its own adaptation set, and stream a representation that is one file with range requests. It throws DashUnsupportedException.
API Changes🔴 #274: Made XmlMini and XmlElement internal, and wrote KDoc for the service package, AudioConfig and the DASH surface.
Fixes#232: Followed the DASH hierarchy for BaseURL elements and merged segment templates from the Period down.
#241: Kept the whole URL out of reconnect warnings and of the messages of the reader, the DASH door and subtitle refusals.
#242: Limited the waits of Dash.manifest and every segment fetch with an HttpReaderPolicy.
#262: Published the byte cache window as one pair, so a buffered-range sample after a far seek is consistent.
#283: Accepted an HTTP answer only when it proves its byte position, its file size and its entity tag.
#284: Resolved DASH URLs against the base path only, so a slash in a manifest URL's query no longer joins the directory.
🔴 264fbf6: Applied DashUrlPolicy to every DASH redirect before following it, with at most five redirects. Under DashUrlPolicy.SameOrigin a redirect to another scheme, host or port fails with DashUrlRefusedException.
Views and Compose UI (6)New Features🔴 #238: Added keepDisplayAwake, true by default, to KitePlayerVideo, KitePlayerSurface, KitePlayerView and KitePlayerUIView, so the display stays awake while video plays.
API Changes#266: Made the notification, screen reader and desktop menu text configurable, with the same English default.
#267: Named installMobileRenderer() in the deprecation messages of the old phone views.
🔴 #309: Added accessibilityVideoLabel and accessibilityStateFormat to KitePlayerVideo and KitePlayerSurface, so an app can translate what a screen reader says.
Fixes#307: Gave the Compose canvas path of KitePlayerVideo a screen reader name and state, and made the platform views update their state by themselves.
#310: Gave KitePlayerAwtView an accessible name and description, with accessibilityVideoLabel, accessibilityStateFormat and updateAccessibilityState().
Android (7)Fixes#201: Dropped remote presses that the player refuses, instead of crashing on a refused lock-screen seek or a remote play after close.
#205: Made attachMediaNotification hold no wake lock instead of refusing a manifest without WAKE_LOCK. KiteLog.log is now public.
#281: Made background handling that attaches after an Activity started see every transition.
#282: Held Android audio focus only when it is granted, and asked for it again after a loss.
#300: Drew the paused picture again when the Android view gets a new surface.
41197ac: Applied a change of secure to a view that already shows video by recreating its surface.
d2c38ad: Routed the three direct android.util.Log calls through KiteLog, so the player prints nothing until an app installs a sink.
Apple (4)API Changes🔴 #204: Declared @Throws on every KitePlayer call that can fail, so Swift catches the failure instead of ending the app.
Performance#207: Copied native frame planes and libass packs with one memcpy instead of one byte at a time.
Fixes#259: Scaled ten-bit video by four in the Metal renderer instead of dividing by 1023.
🔴 e54f920: Failed a macOS player bound to an output device with PlaybackError.AudioDeviceUnavailable when that device disappears. AudioSinkEvent gained Failed.
Desktop JVM (5)New Features🔴 #237: Decoded H.264 and HEVC with VideoToolbox on macOS under HwdecPolicy.Auto, and AV1 on a Mac with AV1 hardware.
Fixes#224: Finished every software frame conversion even when all Dispatchers.Default threads convert.
#225: Painted the desktop AWT view from one thread at a time.
#279: Turned rotated video in the desktop AWT view instead of squeezing it sideways.
#290: Reported an AWT frame as presented only after it reached the screen.
Build and packaging (6)API Changes🔴 #97: Made KiteFFmpegVideoFrame.frame and AudioSamples internal, so the media library's Frame is gone from kiteplayer-ffmpeg. Every Android half now compiles against SDK 37, and all four modules with an expect class pass -Xexpect-actual-classes.
#268: Emitted Java 11 bytecode from every JVM and Android half, so Java 11 apps can use the library.
Fixes#248: Named the bundled licences in the kiteplayer-libass and subtitles POMs.
#250: Shipped the licence notices of the ported visualiser drawings in NOTICE and in the JVM and Android artifacts.
#265: Gave each published module its own POM description.
#270: Retried the libass chain download after a transient failure.
Tests and CI (21)Fixes#249: Pinned the third-party workflow actions to commit SHAs.
#251: Made the test tasks outside the gate pass.
#305: Compiled the device test sources in the emulator job's early build step.
Tests#12: Measured the five Android subtitle styling cases on the images the rasterizer draws, on the CI emulator.
#19: Checked the secure video surface in a screenshot on the CI emulator.
#20: Entered picture in picture from the sample's direct view on the CI emulator, with an upright and a quarter-turned clip.
#28: Ran the tvOS and watchOS simulator tests of core, subtitles, io and rt in CI.
#29: Ran the linuxArm64 tests on an arm64 runner and the Android native tests on the emulator, with test counts in the run summary.
#59: Ran the format matrix in a headless browser, with each row naming its video decoder.
#81: Ran the Android device tests on every push in an emulator job.
#196: Recorded the test clips with FFmpeg 9, the major the player decodes with.
#269: Made the libass end-to-end test poll for its result instead of sleeping a fixed time.
#271: Ran the common tests of audioviz, phone and sample-shared on the Android host.
#302: Made the CoreAudio real-time test wait for the device instead of sleeping.
#328: Rendered every drawing on a one-pixel and a sliver canvas in a test.
#329: Checked that a restarted drawing draws like a fresh one.
#341: Made the audioviz tests skip honestly and fail when they should.
#348: Tested that every drawing looks and moves the same at other refresh rates.
#364: Made the visualiser tests that could not fail able to fail.
#366: Asked the contact sheet test for the catalogue the library has, which is 24 drawings.
#367: Tested every drawing against a shuffle of what happens to a real surface.
Docs (14)Docs#206: Named the file timestamp category in the README's iOS privacy manifest note.
#252: Stated the three LGPL duties in the README and corrected the component list in NOTICE.
#253: Rebuilt the README's target table from what CI and the scripts run.
#254: Gave the flash safety paragraph the counts of the current 24 drawings.
#272: Added kiteplayer-libass to the API reference and fixed 43 unresolved KDoc links.
#273: Corrected doc statements and KDoc samples that the code contradicted.
#275: Opened the README with a runnable example and a target table per artifact.
#303: Said in the README that network media does not play in the browser yet.
#319: Described Marble as it is in the rendering document.
#335: Corrected the audio visualiser documents against the code.
#340: Named the visualiser settings for reduced motion, rate and visibility in the README.
#347: Stated the real numbers and lifetimes in the visualiser comments and documents.
#363: Corrected the Kaleidoscope comment about the bar clock under a pause.
#372: Stated the current values in comments and removed an unused helper.

Dependencies

Version
Kotlin 2.4.20
Compose Multiplatform 1.12.1 (the Compose modules and kiteplayer-audioviz)
Android Gradle Plugin 9.4.1 (built with)
Gradle 9.8.0 (built with)
JDK 21 (built with), JVM bytecode 11
Android SDK compileSdk 37, minSdk 26
KiteFFmpeg 0.4.0, with FFmpeg 9.0.2 (kiteplayer-ffmpeg)
Ktor 3.6.0 (kiteplayer-network)
libass 0.17.5, with HarfBuzz 14.5.0, FreeType 2.14.3 and FriBidi 1.0.17 (kiteplayer-libass)
Kite3D 0.2.0 (kiteplayer-audioviz)
kotlinx.coroutines 1.11.0

Targets: kiteplayer builds for Android, JVM, iOS, macOS, Linux, Windows, JS and Wasm, and kiteplayer-core adds tvOS, watchOS and Android Native. kiteplayer-compose and kiteplayer-audioviz build for Android, JVM and iOS. The Linux and Windows native builds have no audio or video output and no HTTPS, by design.

Full changelog: v0.0.27...v0.2.0