Recently Written · git

subread-overlay

Shows the lines of an .srt over any Android media player, in time with it. Tap a word to look it up.

git clone https://github.com/equwal/subread-overlay

Log | Files | Refs


commit 7d3a96c52c2f50f3e362c95cbe1c9d27414dfc6f
equwal <truex@equwal.com>
2026-09-21 17:27:04 -0700

Add "Share" to the panel

"Share" opens the share sheet of Android with the selection, for an
app that has no entry in the text selection menu: a flash card app, a
notes app, a translator. The player pauses, the same as for "Look up".

 README.md                                                       | 3 ++-
 app/src/androidTest/kotlin/space/subread/overlay/OverlayTest.kt | 6 ++++++
 app/src/main/kotlin/space/subread/overlay/MediaListener.kt      | 5 +++++
 app/src/main/kotlin/space/subread/overlay/OverlayView.kt        | 7 +++++++
 app/src/main/kotlin/space/subread/overlay/Store.kt              | 5 +++++
 fastlane/metadata/android/en-US/full_description.txt            | 2 +-
 6 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index c7c2dab..8146070 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,8 @@ On the panel:
 - `≡` moves the panel.
 - A tap selects the word under the finger. A drag selects more words.
   "Look up" pauses the player and sends the selection to the dictionary.
-  "Copy" copies it.
+  "Share" opens the share sheet of Android, for an app without an entry in
+  the text selection menu. "Copy" copies it.
 - `▶` starts the player again after a lookup. `⏸` pauses it.
 - `⋯` opens the timing row. `−0.5 s` and `+0.5 s` shift the subtitles.
   `◀ line` and `line ▶` make the line before, or the next line, the line of
diff --git a/app/src/androidTest/kotlin/space/subread/overlay/OverlayTest.kt b/app/src/androidTest/kotlin/space/subread/overlay/OverlayTest.kt
index cdbbaae..d62659d 100644
--- a/app/src/androidTest/kotlin/space/subread/overlay/OverlayTest.kt
+++ b/app/src/androidTest/kotlin/space/subread/overlay/OverlayTest.kt
@@ -66,11 +66,13 @@ class WordsTest {
 class OverlayViewTest {
 
     private val lookedUp = CopyOnWriteArrayList<String>()
+    private val shared = CopyOnWriteArrayList<String>()
     private val events = object : OverlayView.Events {
         override fun onDrag(dx: Float, dy: Float) = Unit
         override fun onDragEnd() = Unit
         override fun onClose() = Unit
         override fun onLookUp(word: String) { lookedUp += word }
+        override fun onShare(word: String) { shared += word }
         override fun onTogglePlay() = Unit
         override fun onShiftLines(steps: Int) = Unit
         override fun onNudge(ms: Long) = Unit
@@ -112,6 +114,10 @@ class OverlayViewTest {
                 panel.findViewsWithText(found, "Look up", android.view.View.FIND_VIEWS_WITH_TEXT)
                 found.single().performClick()
                 assertEquals(listOf("along is easy"), lookedUp)
+                found.clear()
+                panel.findViewsWithText(found, "Share", android.view.View.FIND_VIEWS_WITH_TEXT)
+                found.single().performClick()
+                assertEquals(listOf("along is easy"), shared)
 
                 // The next line has no selection from the line before.
                 panel.showLine("the next line")
diff --git a/app/src/main/kotlin/space/subread/overlay/MediaListener.kt b/app/src/main/kotlin/space/subread/overlay/MediaListener.kt
index c5a29aa..42914e7 100644
--- a/app/src/main/kotlin/space/subread/overlay/MediaListener.kt
+++ b/app/src/main/kotlin/space/subread/overlay/MediaListener.kt
@@ -142,6 +142,11 @@ class MediaListener : NotificationListenerService(), OverlayView.Events {
         }
     }
 
+    override fun onShare(word: String) {
+        follower.pause()
+        runCatching { startActivity(Lookup.share(word)) }
+    }
+
     override fun onTogglePlay() {
         if (!follower.pause()) follower.play()
     }
diff --git a/app/src/main/kotlin/space/subread/overlay/OverlayView.kt b/app/src/main/kotlin/space/subread/overlay/OverlayView.kt
index 268d968..5c8ec7c 100644
--- a/app/src/main/kotlin/space/subread/overlay/OverlayView.kt
+++ b/app/src/main/kotlin/space/subread/overlay/OverlayView.kt
@@ -37,6 +37,8 @@ class OverlayView(context: Context, private val events: Events) : LinearLayout(c
         fun onDragEnd()
         fun onClose()
         fun onLookUp(word: String)
+        /** "Share": the selection goes to an app that the user picks in the share sheet. */
+        fun onShare(word: String)
         /** The play button: pause the player when it plays, else start it. */
         fun onTogglePlay()
         fun onShiftLines(steps: Int)
@@ -85,6 +87,7 @@ class OverlayView(context: Context, private val events: Events) : LinearLayout(c
 
         lookUpRow.gravity = Gravity.END
         lookUpRow.addView(button("Copy", null) { copy() }, wrap())
+        lookUpRow.addView(button("Share", null) { share() }, wrap())
         lookUpRow.addView(button("Look up", null) { lookUp() }, wrap())
         lookUpRow.visibility = View.GONE
         addView(lookUpRow, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
@@ -193,6 +196,10 @@ class OverlayView(context: Context, private val events: Events) : LinearLayout(c
         events.onLookUp(selected() ?: return)
     }
 
+    private fun share() {
+        events.onShare(selected() ?: return)
+    }
+
     private fun copy() {
         val word = selected() ?: return
         context.getSystemService(ClipboardManager::class.java).setPrimaryClip(ClipData.newPlainText("subtitle", word))
diff --git a/app/src/main/kotlin/space/subread/overlay/Store.kt b/app/src/main/kotlin/space/subread/overlay/Store.kt
index 6180d97..8beabc7 100644
--- a/app/src/main/kotlin/space/subread/overlay/Store.kt
+++ b/app/src/main/kotlin/space/subread/overlay/Store.kt
@@ -68,6 +68,11 @@ object Lookup {
     /** The intent that each app with "process text" in its manifest takes. It is what a text selection menu sends. */
     fun probe(): Intent = Intent(Intent.ACTION_PROCESS_TEXT).setType("text/plain")
 
+    /** The share sheet of Android: each app that takes a text, also one without "process text". */
+    fun share(word: String): Intent =
+        Intent.createChooser(Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, word), null)
+            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
     fun intent(word: String, chosen: ComponentName?): Intent {
         val send = probe()
             .putExtra(Intent.EXTRA_PROCESS_TEXT, word)
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
index 8b52840..1147b32 100644
--- a/fastlane/metadata/android/en-US/full_description.txt
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -3,7 +3,7 @@ SubRead Overlay shows the lines of a subtitle file (.srt) over the app that play
 <b>What it does</b>
 
 * Works with each player that publishes an Android media session: Voice, VLC, mpv-android, podcast apps and more. No player needs a change.
-* Tap a word to select it. Drag to select more words. "Look up" pauses the player and sends the selection to a dictionary, a translator or a flash card app. Each app with an entry in the text selection menu can be the target. A button on the panel starts the player again.
+* Tap a word to select it. Drag to select more words. "Look up" pauses the player and sends the selection to a dictionary, a translator or a flash card app. Each app with an entry in the text selection menu can be the target. "Share" opens the share sheet of Android for the other apps. A button on the panel starts the player again.
 * On request the panel shows the line before and the line after too, and lets the player show through.
 * The line stays on the panel until the next line starts, so there is time to look a word up.
 * A timing row shifts the subtitles by half a second, or makes the line before or the next line the line of now.