diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md
new file mode 100644
index 00000000..72bfa529
--- /dev/null
+++ b/THIRD-PARTY-NOTICES.md
@@ -0,0 +1,55 @@
+# Third-Party Notices
+
+This project bundles third-party assets, listed below with their licenses.
+
+## Lucide icons
+
+The audio and video file-type icons in `frontend/src/main/resources/icons/`
+(`audio.svg`, `audio_dark.svg`, `video.svg`, `video_dark.svg`) are adapted from
+[Lucide](https://lucide.dev) — the `music` and `film` icons, recolored and
+resized for use in the IDE.
+
+`film` is covered by Lucide's ISC license. `music` is one of Lucide's
+Feather-derived icons and is covered by the MIT license. Both notices follow.
+
+### ISC License
+
+```
+Copyright (c) 2026 Lucide Icons and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+```
+
+### MIT License (for Feather-derived icons, including `music`)
+
+```
+Copyright (c) 2013-present Cole Bemis
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
diff --git a/frontend/src/main/kotlin/dev/twango/jetplay/editor/JetplayIcons.kt b/frontend/src/main/kotlin/dev/twango/jetplay/editor/JetplayIcons.kt
new file mode 100644
index 00000000..66196123
--- /dev/null
+++ b/frontend/src/main/kotlin/dev/twango/jetplay/editor/JetplayIcons.kt
@@ -0,0 +1,13 @@
+package dev.twango.jetplay.editor
+
+import com.intellij.openapi.util.IconLoader
+import javax.swing.Icon
+
+object JetplayIcons {
+
+ @JvmField
+ val Audio: Icon = IconLoader.getIcon("/icons/audio.svg", JetplayIcons::class.java)
+
+ @JvmField
+ val Video: Icon = IconLoader.getIcon("/icons/video.svg", JetplayIcons::class.java)
+}
diff --git a/frontend/src/main/kotlin/dev/twango/jetplay/editor/MediaFileIconProvider.kt b/frontend/src/main/kotlin/dev/twango/jetplay/editor/MediaFileIconProvider.kt
new file mode 100644
index 00000000..d42cb3f7
--- /dev/null
+++ b/frontend/src/main/kotlin/dev/twango/jetplay/editor/MediaFileIconProvider.kt
@@ -0,0 +1,19 @@
+package dev.twango.jetplay.editor
+
+import com.intellij.ide.FileIconProvider
+import com.intellij.openapi.project.DumbAware
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import dev.twango.jetplay.media.MediaClassification
+import javax.swing.Icon
+
+class MediaFileIconProvider :
+ FileIconProvider,
+ DumbAware {
+
+ override fun getIcon(file: VirtualFile, flags: Int, project: Project?): Icon? {
+ if (file.fileType != MediaFileType.INSTANCE) return null
+ val extension = file.extension ?: return null
+ return if (MediaClassification.isVideo(extension)) JetplayIcons.Video else JetplayIcons.Audio
+ }
+}
diff --git a/frontend/src/main/resources/dev.twango.jetplay.frontend.xml b/frontend/src/main/resources/dev.twango.jetplay.frontend.xml
index ff48d7ae..2c9c10f9 100644
--- a/frontend/src/main/resources/dev.twango.jetplay.frontend.xml
+++ b/frontend/src/main/resources/dev.twango.jetplay.frontend.xml
@@ -8,6 +8,7 @@
implementationClass="dev.twango.jetplay.editor.MediaFileType"
fieldName="INSTANCE"
extensions="mp4;m4v;mkv;avi;mov;wmv;flv;webm;ogv;mts;m2ts;m2t;3gp;ivf;mp3;wav;ogg;oga;opus;m4a;aac;wma;aiff;aif;flac;ac3;eac3;caf;adts;amr;au;pcmu;ulaw;pcma;alaw;g722;gsm;sln"/>
+
diff --git a/frontend/src/main/resources/icons/audio.svg b/frontend/src/main/resources/icons/audio.svg
new file mode 100644
index 00000000..fc937895
--- /dev/null
+++ b/frontend/src/main/resources/icons/audio.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/src/main/resources/icons/audio_dark.svg b/frontend/src/main/resources/icons/audio_dark.svg
new file mode 100644
index 00000000..c8b7f8e7
--- /dev/null
+++ b/frontend/src/main/resources/icons/audio_dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/src/main/resources/icons/video.svg b/frontend/src/main/resources/icons/video.svg
new file mode 100644
index 00000000..5b290983
--- /dev/null
+++ b/frontend/src/main/resources/icons/video.svg
@@ -0,0 +1,10 @@
+
diff --git a/frontend/src/main/resources/icons/video_dark.svg b/frontend/src/main/resources/icons/video_dark.svg
new file mode 100644
index 00000000..7ffca897
--- /dev/null
+++ b/frontend/src/main/resources/icons/video_dark.svg
@@ -0,0 +1,10 @@
+
diff --git a/frontend/src/test/kotlin/dev/twango/jetplay/editor/MediaFileIconResourcesTest.kt b/frontend/src/test/kotlin/dev/twango/jetplay/editor/MediaFileIconResourcesTest.kt
new file mode 100644
index 00000000..c1a7120e
--- /dev/null
+++ b/frontend/src/test/kotlin/dev/twango/jetplay/editor/MediaFileIconResourcesTest.kt
@@ -0,0 +1,19 @@
+package dev.twango.jetplay.editor
+
+import org.junit.Assert.assertNotNull
+import org.junit.Test
+
+class MediaFileIconResourcesTest {
+
+ @Test
+ fun audioAndVideoIconsArePresentInBothThemes() {
+ listOf(
+ "/icons/audio.svg",
+ "/icons/audio_dark.svg",
+ "/icons/video.svg",
+ "/icons/video_dark.svg",
+ ).forEach { path ->
+ assertNotNull("missing icon resource $path", javaClass.getResource(path))
+ }
+ }
+}
diff --git a/shared/src/main/kotlin/dev/twango/jetplay/media/MediaClassification.kt b/shared/src/main/kotlin/dev/twango/jetplay/media/MediaClassification.kt
index dd8fecc4..0c140d97 100644
--- a/shared/src/main/kotlin/dev/twango/jetplay/media/MediaClassification.kt
+++ b/shared/src/main/kotlin/dev/twango/jetplay/media/MediaClassification.kt
@@ -2,7 +2,8 @@ package dev.twango.jetplay.media
object MediaClassification {
- // Keep in sync with the video extensions in plugin.xml.
+ // Video extensions registered by the media file type in the frontend descriptor.
+ // .ts is excluded to avoid clobbering TypeScript files.
private val VIDEO_EXTENSIONS = setOf(
"mp4",
"m4v",
@@ -13,9 +14,9 @@ object MediaClassification {
"flv",
"webm",
"ogv",
- "ts",
"mts",
"m2ts",
+ "m2t",
"3gp",
"ivf",
)
diff --git a/shared/src/test/kotlin/dev/twango/jetplay/media/MediaClassificationTest.kt b/shared/src/test/kotlin/dev/twango/jetplay/media/MediaClassificationTest.kt
index fae46249..c444757f 100644
--- a/shared/src/test/kotlin/dev/twango/jetplay/media/MediaClassificationTest.kt
+++ b/shared/src/test/kotlin/dev/twango/jetplay/media/MediaClassificationTest.kt
@@ -62,6 +62,13 @@ class MediaClassificationTest {
assertFalse(MediaClassification.isVideo("opus"))
}
+ @Test
+ fun transportStreamContainersClassifyAsVideo() {
+ assertTrue(MediaClassification.isVideo("mts"))
+ assertTrue(MediaClassification.isVideo("m2ts"))
+ assertTrue(MediaClassification.isVideo("m2t"))
+ }
+
@Test
fun rawAudioExtensionsNeedTranscoding() {
MediaClassification.rawAudioExtensions.forEach {