Skip to content

feat: support raw telephony codecs and additional container formats#31

Merged
twangodev merged 7 commits into
mainfrom
feat/raw-audio-formats
May 17, 2026
Merged

feat: support raw telephony codecs and additional container formats#31
twangodev merged 7 commits into
mainfrom
feat/raw-audio-formats

Conversation

@twangodev
Copy link
Copy Markdown
Owner

@twangodev twangodev commented May 16, 2026

Adds PCMU/PCMA/G.722/GSM/SLN raw audio (with explicit demuxer hints for headerless streams) plus AMR, AU, and MPEG-TS/3GP video extensions.

Summary by CodeRabbit

  • New Features
    • Added support for additional audio/video formats (TS, M2TS, 3GP, Opus, ALAW, G722, AMR, AU, PCMU, ULAW, PCMA).
    • Improved handling of headerless/raw audio for more reliable playback/transcoding.
  • Refactor
    • More reliable progress reporting and safer start/stop handling during transcoding.
  • Tests
    • Added a test ensuring raw-audio extensions are registered in plugin metadata.
  • Chores
    • Bumped IntelliJ platform tooling version.
  • CI
    • Split plugin verification to run per-IDE with a helper script to discover IDE targets.

Review Change Stack

dependabot Bot and others added 2 commits May 1, 2026 15:30
Bumps org.jetbrains.intellij.platform from 2.14.0 to 2.16.0.

---
updated-dependencies:
- dependency-name: org.jetbrains.intellij.platform
  dependency-version: 2.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Adds PCMU/PCMA/G.722/GSM/SLN raw audio (with explicit demuxer hints
for headerless streams) plus AMR, AU, and MPEG-TS/3GP video extensions.
Copilot AI review requested due to automatic review settings May 16, 2026 17:47
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

📝 Walkthrough

Walkthrough

Adds RawAudioHint and RAW_AUDIO_HINTS to configure FFmpegFrameGrabber for headerless/raw audio; refactors MediaTranscoder frame-loop and progress; expands plugin.xml media extensions and adds a test; bumps intelliJPlatform in the Gradle catalog; makes plugin verification run per-IDE via discovery and Gradle pinning.

Changes

Raw audio codec support

Layer / File(s) Summary
Transcoder hints, lifecycle, and frame loop
src/main/kotlin/dev/twango/jetplay/transcode/MediaTranscoder.kt
Adds INDETERMINATE_TENTH, RawAudioHint, RAW_AUDIO_HINTS, and rawAudioExtensions; applies raw hints to FFmpegFrameGrabber; refactors transcode into runFrameLoop, reportProgress, and safely; extracts buildRecorder and manages grabber/recorder lifecycle with start flags.
Plugin.xml extensions and registration test
src/main/resources/META-INF/plugin.xml, src/test/kotlin/dev/twango/jetplay/transcode/MediaTranscoderTest.kt
Expands the Media file type extensions attribute and adds a test that asserts all MediaTranscoder.rawAudioExtensions are present in plugin.xml.
Gradle IntelliJ platform version bump
gradle/libs.versions.toml
Updates intelliJPlatform version reference from 2.14.0 to 2.16.0.

Plugin verification per-IDE

Layer / File(s) Summary
IDE discovery script
.github/scripts/list-verifier-ides.sh
Adds a script that runs ./gradlew -q printProductsReleases and converts the newline list into a JSON array for workflow matrices.
Workflow: discover and matrix verify
.github/workflows/build.yml
Adds discover-ides job and converts verify into a matrix job that runs ./gradlew verifyPlugin -PverifierIde=${{ matrix.ide }} per discovered IDE and uploads per-IDE artifacts.
Gradle verifier pinning
build.gradle.kts
Reads optional verifierIde property; if set parses TYPE-VERSION, validates it, and registers the pinned IDE via create(type, version), otherwise falls back to recommended().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibble bytes and tweak the stream,
Raw PCM hums into my dream,
Hints applied, frames loop and flow,
Extensions listed — let playback grow. 🎶

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding support for raw telephony codecs (PCMU, PCMA, G.722, etc.) and additional container formats (AMR, AU, MPEG-TS, 3GP). It matches the primary objectives evident in the MediaTranscoder refactoring, plugin.xml extensions, and related changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/raw-audio-formats

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/kotlin/dev/twango/jetplay/transcode/MediaTranscoder.kt`:
- Around line 41-49: RAW_AUDIO_HINTS in MediaTranscoder.kt duplicates extension
keys that should be maintained in plugin.xml; instead of hardcoding the map,
remove the literal RAW_AUDIO_HINTS map and load the audio hint data from the
central extension registry (or parse plugin.xml) at runtime—use the existing
RawAudioHint data class and a helper method (e.g., in MediaTranscoder or a new
provider) to look up hints by extension name so the code references the
canonical source of supported extensions rather than duplicating entries.
- Around line 56-63: The FFmpeg native resources can leak because
FFmpegFrameGrabber.start() and recorder.start() may throw before
stop()/release() are called; update MediaTranscoder to allocate grabber and
recorder, then call grabber.start() and recorder.start() inside try blocks with
corresponding finally blocks that always call grabber.stop()/grabber.release()
and recorder.stop()/recorder.release() (guarded by null and/or "started"
booleans) so cleanup runs even on startup exceptions; apply the same safe
lifecycle pattern around the code paths that currently use FFmpegFrameGrabber
(grabber) and the recorder (recorder) in the method to ensure native resources
are freed on error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4b10d3fa-e93e-45a3-87cd-6876b3b775ae

📥 Commits

Reviewing files that changed from the base of the PR and between 8ecc905 and 79b71ef.

📒 Files selected for processing (2)
  • src/main/kotlin/dev/twango/jetplay/transcode/MediaTranscoder.kt
  • src/main/resources/META-INF/plugin.xml

Comment thread src/main/kotlin/dev/twango/jetplay/transcode/MediaTranscoder.kt
Comment thread src/main/kotlin/dev/twango/jetplay/transcode/MediaTranscoder.kt Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@twangodev twangodev enabled auto-merge (squash) May 16, 2026 17:50
@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

❌ Patch coverage is 20.40816% with 39 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...in/dev/twango/jetplay/transcode/MediaTranscoder.kt 20.40% 39 Missing ⚠️
Files with missing lines Coverage Δ
...in/dev/twango/jetplay/transcode/MediaTranscoder.kt 26.92% <20.40%> (+8.58%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Wrap grabber/recorder lifecycle in a single try/finally with started flags
so stop()/release() always run even if start() throws. Extract helpers
(applyRawHints, buildRecorder, runFrameLoop, reportProgress, safely) to
remove nested try/finally and keep transcode() at two indent levels.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 16, 2026

Qodana Community for JVM

It seems all right 👌

No new problems were found according to the checks applied

💡 Qodana analysis was run in the pull request mode: only the changed files were checked

View the detailed Qodana report

To be able to view the detailed Qodana report, you can either:

To get *.log files or any other Qodana artifacts, run the action with upload-result option set to true,
so that the action will upload the files as the job artifacts:

      - name: 'Qodana Scan'
        uses: JetBrains/qodana-action@v2026.1.0
        with:
          upload-result: true
Contact Qodana team

Contact us at qodana-support@jetbrains.com

twangodev added 2 commits May 16, 2026 12:40
Reads the plugin.xml resource at test time and fails the build if any
raw-audio hint extension is missing from the registered media file type,
catching drift between the two files before it ships.
….0 (#29)

Merged to pick up newer IntelliJ Plugin Verifier, which addresses
ClosedFileSystemException races seen in verifyPlugin on 2.14.0.
Copilot AI review requested due to automatic review settings May 16, 2026 19:41
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/test/kotlin/dev/twango/jetplay/transcode/MediaTranscoderTest.kt (1)

53-56: ⚡ Quick win

Make the plugin.xml assertion tolerant of formatting-only changes.

This regex only matches extensions="..." with no surrounding whitespace, and the parsed tokens are not trimmed. A simple XML reformat can fail the test even when the registered extensions are unchanged.

Suggested fix
-        val match = Regex("""extensions="([^"]+)"""").find(xml)
+        val match = Regex("""extensions\s*=\s*"([^"]+)"""").find(xml)
             ?: error("Could not find extensions attribute in plugin.xml")
-        val registered = match.groupValues[1].split(";").map { it.lowercase() }.toSet()
+        val registered = match.groupValues[1]
+            .split(";")
+            .map { it.trim().lowercase() }
+            .filter { it.isNotEmpty() }
+            .toSet()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/kotlin/dev/twango/jetplay/transcode/MediaTranscoderTest.kt` around
lines 53 - 56, The test in MediaTranscoderTest reads the plugin.xml into
variable xml and uses Regex("""extensions="([^"]+)"""") which fails on
formatting changes and leaves tokens untrimmed; update the pattern to allow
optional whitespace around the equals and quotes (e.g. use
Regex("""extensions\s*=\s*"([^"]*)" """) or parse the attribute with a
lightweight XML/HTML parser), then split the captured group, trim each token,
filter out empty strings and lowercase them before building the registered set
so the assertion is robust to formatting-only changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/kotlin/dev/twango/jetplay/transcode/MediaTranscoder.kt`:
- Around line 130-146: The indeterminate-duration branch in reportProgress never
emits the initial onProgress(-1.0) because lastReportedTenth is initialized to
INDETERMINATE_TENTH and the branch checks the opposite; change the
totalMicroseconds <= 0 branch in reportProgress to emit onProgress(-1.0) when
lastReportedTenth == INDETERMINATE_TENTH (i.e., first time seeing an unknown
duration) and then return a different sentinel (e.g.,
REPORTED_INDETERMINATE_TENTH or any non-INDETERMINATE value) so subsequent calls
don't repeat the emission; update or add the sentinel constant and use it in the
caller loop that stores the returned value from reportProgress
(lastReportedTenth) to prevent duplicate indeterminate notifications.

---

Nitpick comments:
In `@src/test/kotlin/dev/twango/jetplay/transcode/MediaTranscoderTest.kt`:
- Around line 53-56: The test in MediaTranscoderTest reads the plugin.xml into
variable xml and uses Regex("""extensions="([^"]+)"""") which fails on
formatting changes and leaves tokens untrimmed; update the pattern to allow
optional whitespace around the equals and quotes (e.g. use
Regex("""extensions\s*=\s*"([^"]*)" """) or parse the attribute with a
lightweight XML/HTML parser), then split the captured group, trim each token,
filter out empty strings and lowercase them before building the registered set
so the assertion is robust to formatting-only changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d01ebb8c-76c8-4634-9d00-8073c233a002

📥 Commits

Reviewing files that changed from the base of the PR and between 79b71ef and 82179e9.

📒 Files selected for processing (3)
  • gradle/libs.versions.toml
  • src/main/kotlin/dev/twango/jetplay/transcode/MediaTranscoder.kt
  • src/test/kotlin/dev/twango/jetplay/transcode/MediaTranscoderTest.kt
✅ Files skipped from review due to trivial changes (1)
  • gradle/libs.versions.toml

Comment thread src/main/kotlin/dev/twango/jetplay/transcode/MediaTranscoder.kt Outdated
twangodev added 2 commits May 16, 2026 22:01
reportProgress never emitted onProgress(-1.0) for unknown-duration
inputs because lastReportedTenth started at INDETERMINATE_TENTH and
the branch only emitted when it wasn't. Flip the check and return a
distinct REPORTED_INDETERMINATE_TENTH sentinel so the notification
fires once on first sight and never repeats.

Also widen the plugin.xml extensions regex to tolerate whitespace
around '=' and trim/filter token splits, so the registration test
isn't sensitive to formatting-only edits.
The single-runner verifyPlugin job spawned all recommended() IDEs as
parallel workers under one verifier process, and a transient failure
in any worker would interrupt the others mid-JAR-read, surfacing as
ClosedByInterruptException cascades that obscure the real cause.

Split it into a discover-ides job that calls printProductsReleases
(via .github/scripts/list-verifier-ides.sh, which emits the same
recommended() set as a JSON array) and a matrix verify job that
pins each runner to a single IDE through -PverifierIde. With
fail-fast: false, one IDE's failure no longer hides the others, and
the shared JAR-filesystem cache can't cascade across runners.
Copilot AI review requested due to automatic review settings May 17, 2026 17:56
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
.github/workflows/build.yml (2)

322-322: 💤 Low value

Artifact names must comply with GitHub Actions restrictions.

The artifact name includes ${{ matrix.ide }} (e.g., pluginVerifier-IU-2025.2.6.2). GitHub Actions artifact names cannot contain :, <, >, |, *, ?, \, /, or control characters. The TYPE-VERSION format with dots and dashes should be safe, but if the discovery script ever returns IDEs with different formats, uploads could fail.

🛡️ Sanitize artifact names
         with:
-          name: pluginVerifier-${{ matrix.ide }}
+          name: pluginVerifier-${{ matrix.ide }}-report
           path: ${{ github.workspace }}/build/reports/pluginVerifier

Or explicitly sanitize:

      - name: Sanitize IDE name for artifact
        id: sanitize
        run: echo "ide_safe=$(echo '${{ matrix.ide }}' | tr -cd '[:alnum:].-')" >> "$GITHUB_OUTPUT"
      
      - name: Collect Plugin Verifier Result
        uses: actions/upload-artifact@v6
        with:
          name: pluginVerifier-${{ steps.sanitize.outputs.ide_safe }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build.yml at line 322, The artifact name uses
pluginVerifier-${{ matrix.ide }} which can include illegal characters; add a
sanitization step before the upload to produce a safe variable (e.g., step id
"sanitize" producing outputs.ide_safe) that strips disallowed chars (allow only
alphanumerics, dots, dashes) and then change the upload-artifact name to
pluginVerifier-${{ steps.sanitize.outputs.ide_safe }} so all artifact uploads
use the sanitized IDE name.

271-274: ⚡ Quick win

Consider guarding against empty IDE arrays.

If the discovery script returns an empty JSON array [], the matrix job will skip all verification runs silently. While this might be intentional, it could mask configuration issues.

🛡️ Add validation for empty IDE list

You could add a validation step to the discover-ides job after the list step:

      - name: List verifier IDEs
        id: list
        run: echo "ides=$(.github/scripts/list-verifier-ides.sh)" >> "$GITHUB_OUTPUT"

      - name: Validate IDE list
        run: |
          IDES='${{ steps.list.outputs.ides }}'
          if [ "$(echo "$IDES" | jq '. | length')" -eq 0 ]; then
            echo "::error::No IDEs discovered for verification"
            exit 1
          fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build.yml around lines 271 - 274, The matrix uses
needs.discover-ides.outputs.ides to populate the ide matrix but doesn't guard
against an empty array; update the discover-ides job (the step that sets ides,
e.g., step id "list" / the "List verifier IDEs" step) to add a validation step
after producing the ides output that parses steps.list.outputs.ides (with jq or
similar) and fails the job with a clear error if the array length is 0 so the
workflow does not silently skip all matrix runs.
build.gradle.kts (1)

102-109: ⚡ Quick win

Validate that type and version are non-empty after splitting.

The current validation checks that the split produces exactly two parts but doesn't verify that neither part is blank. If a user provides verifierIde=IU- or verifierIde=-2025.2, the validation passes but create(type, version) will likely fail with a less clear error message.

♻️ Enhanced validation
             val pinned = providers.gradleProperty("verifierIde").orNull
             if (pinned.isNullOrBlank()) {
                 recommended()
             } else {
                 val (type, version) = pinned.split("-", limit = 2)
-                    .also { require(it.size == 2) { "verifierIde must be 'TYPE-VERSION' (e.g. IU-2025.2.6.2), got '$pinned'" } }
+                    .also { 
+                        require(it.size == 2) { "verifierIde must be 'TYPE-VERSION' (e.g. IU-2025.2.6.2), got '$pinned'" }
+                        require(it[0].isNotBlank() && it[1].isNotBlank()) { 
+                            "verifierIde type and version must be non-empty, got '$pinned'" 
+                        }
+                    }
                 create(type, version)
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build.gradle.kts` around lines 102 - 109, After splitting pinned from
providers.gradleProperty("verifierIde") into (type, version), validate that both
type and version are non-empty and not blank before calling create(type,
version) (currently in the branch that handles pinned). If either is blank,
throw a clear IllegalArgumentException (or use require) with a message like
"verifierIde must be 'TYPE-VERSION' with non-empty TYPE and VERSION, got
'<pinned>'"; otherwise call create(type, version). Ensure recommended() behavior
remains unchanged when pinned is blank.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.github/workflows/build.yml:
- Line 322: The artifact name uses pluginVerifier-${{ matrix.ide }} which can
include illegal characters; add a sanitization step before the upload to produce
a safe variable (e.g., step id "sanitize" producing outputs.ide_safe) that
strips disallowed chars (allow only alphanumerics, dots, dashes) and then change
the upload-artifact name to pluginVerifier-${{ steps.sanitize.outputs.ide_safe
}} so all artifact uploads use the sanitized IDE name.
- Around line 271-274: The matrix uses needs.discover-ides.outputs.ides to
populate the ide matrix but doesn't guard against an empty array; update the
discover-ides job (the step that sets ides, e.g., step id "list" / the "List
verifier IDEs" step) to add a validation step after producing the ides output
that parses steps.list.outputs.ides (with jq or similar) and fails the job with
a clear error if the array length is 0 so the workflow does not silently skip
all matrix runs.

In `@build.gradle.kts`:
- Around line 102-109: After splitting pinned from
providers.gradleProperty("verifierIde") into (type, version), validate that both
type and version are non-empty and not blank before calling create(type,
version) (currently in the branch that handles pinned). If either is blank,
throw a clear IllegalArgumentException (or use require) with a message like
"verifierIde must be 'TYPE-VERSION' with non-empty TYPE and VERSION, got
'<pinned>'"; otherwise call create(type, version). Ensure recommended() behavior
remains unchanged when pinned is blank.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff352a2f-5dd3-43ac-b054-82ebd014b199

📥 Commits

Reviewing files that changed from the base of the PR and between 1fea51a and 1782db8.

📒 Files selected for processing (3)
  • .github/scripts/list-verifier-ides.sh
  • .github/workflows/build.yml
  • build.gradle.kts
✅ Files skipped from review due to trivial changes (1)
  • .github/scripts/list-verifier-ides.sh

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@twangodev twangodev merged commit 13d3955 into main May 17, 2026
29 of 30 checks passed
@twangodev twangodev deleted the feat/raw-audio-formats branch May 17, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants