Skip to content

Commit

Permalink
Display transcript text and follow along the audio (#7103)
Browse files Browse the repository at this point in the history
  • Loading branch information
tonytamsf committed May 18, 2024
1 parent 1edceb1 commit 1bb3ca4
Show file tree
Hide file tree
Showing 32 changed files with 807 additions and 131 deletions.
9 changes: 7 additions & 2 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:
emulator-test:
name: "Emulator Test"
needs: static-analysis
runs-on: macOS-latest
runs-on: ubuntu-latest
timeout-minutes: 45
env:
api-level: 30
Expand All @@ -124,6 +124,11 @@ jobs:
run: echo "org.gradle.parallel=true" >> local.properties
- name: Build with Gradle
run: ./gradlew assemblePlayDebugAndroidTest
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Android Emulator test
uses: reactivecircus/android-emulator-runner@v2
with:
Expand All @@ -133,7 +138,7 @@ jobs:
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: zsh .github/workflows/runEmulatorTests.sh
script: bash .github/workflows/runEmulatorTests.sh
- uses: actions/upload-artifact@v4
if: failure()
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/runEmulatorTests.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/zsh
#!/bin/bash

set -o pipefail

Expand Down
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ dependencies {
implementation project(':net:ssl')
implementation project(':net:sync:service')
implementation project(':parser:feed')
implementation project(':parser:transcript')
implementation project(':playback:base')
implementation project(':playback:cast')
implementation project(':storage:database')
Expand All @@ -88,6 +89,7 @@ dependencies {
implementation project(':net:sync:service-interface')
implementation project(':playback:service')
implementation project(':ui:chapters')
implementation project(':ui:transcript')

annotationProcessor "androidx.annotation:annotation:$annotationVersion"
implementation "androidx.appcompat:appcompat:$appcompatVersion"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public static boolean onPrepareMenu(Menu menu, FeedItem selectedItem) {
final boolean fileDownloaded = hasMedia && selectedItem.getMedia().fileExists();
final boolean isLocalFile = hasMedia && selectedItem.getFeed().isLocalFeed();
final boolean isFavorite = selectedItem.isTagged(FeedItem.TAG_FAVORITE);
final boolean hasTranscript = selectedItem.hasTranscript();

setItemVisibility(menu, R.id.skip_episode_item, isPlaying);
setItemVisibility(menu, R.id.remove_from_queue_item, isInQueue);
Expand All @@ -84,6 +85,7 @@ public static boolean onPrepareMenu(Menu menu, FeedItem selectedItem) {
setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite);
setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite);
setItemVisibility(menu, R.id.remove_item, fileDownloaded || isLocalFile);
setItemVisibility(menu, R.id.transcript_item, hasTranscript);
return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package de.danoeh.antennapod.ui.screen.playback;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.elevation.SurfaceColors;
import de.danoeh.antennapod.databinding.TranscriptItemBinding;
import de.danoeh.antennapod.event.playback.PlaybackPositionEvent;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.model.feed.TranscriptSegment;
import de.danoeh.antennapod.model.playback.Playable;
import de.danoeh.antennapod.ui.common.Converter;
import de.danoeh.antennapod.ui.transcript.TranscriptViewholder;
import java.util.Set;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.jsoup.internal.StringUtil;

public class TranscriptAdapter extends RecyclerView.Adapter<TranscriptViewholder> {

public String tag = "TranscriptAdapter";
private final SegmentClickListener segmentClickListener;
private final Context context;
private FeedMedia media;
private int prevHighlightPosition = -1;
private int highlightPosition = -1;

public TranscriptAdapter(Context context, SegmentClickListener segmentClickListener) {
this.context = context;
this.segmentClickListener = segmentClickListener;
}

@NonNull
@Override
public TranscriptViewholder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
return new TranscriptViewholder(TranscriptItemBinding.inflate(LayoutInflater.from(context), viewGroup, false));
}

public void setMedia(Playable media) {
if (!(media instanceof FeedMedia)) {
return;
}
this.media = (FeedMedia) media;
notifyDataSetChanged();
}

@Override
public void onBindViewHolder(@NonNull TranscriptViewholder holder, int position) {
if (media == null || media.getTranscript() == null) {
return;
}

TranscriptSegment seg = media.getTranscript().getSegmentAt(position);
holder.viewContent.setOnClickListener(v -> {
if (segmentClickListener != null) {
segmentClickListener.onTranscriptClicked(position, seg);
}
});

String timecode = Converter.getDurationStringLong((int) seg.getStartTime());
if (!StringUtil.isBlank(seg.getSpeaker())) {
if (position > 0 && media.getTranscript()
.getSegmentAt(position - 1).getSpeaker().equals(seg.getSpeaker())) {
holder.viewTimecode.setVisibility(View.GONE);
holder.viewContent.setText(seg.getWords());
} else {
holder.viewTimecode.setVisibility(View.VISIBLE);
holder.viewTimecode.setText(timecode + " • " + seg.getSpeaker());
holder.viewContent.setText(seg.getWords());
}
} else {
Set<String> speakers = media.getTranscript().getSpeakers();
if (speakers.isEmpty() && (position % 5 == 0)) {
holder.viewTimecode.setVisibility(View.VISIBLE);
holder.viewTimecode.setText(timecode);
}
holder.viewContent.setText(seg.getWords());
}

if (position == highlightPosition) {
float density = context.getResources().getDisplayMetrics().density;
holder.viewContent.setBackgroundColor(SurfaceColors.getColorForElevation(context, 32 * density));
holder.viewContent.setAlpha(1.0f);
holder.viewTimecode.setAlpha(1.0f);
holder.viewContent.setAlpha(1.0f);
} else {
holder.viewContent.setBackgroundColor(ContextCompat.getColor(context, android.R.color.transparent));
holder.viewContent.setAlpha(0.5f);
holder.viewTimecode.setAlpha(0.5f);
}
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(PlaybackPositionEvent event) {
if (media == null || media.getTranscript() == null) {
return;
}
int index = media.getTranscript().findSegmentIndexBefore(event.getPosition());
if (index < 0 || index > media.getTranscript().getSegmentCount()) {
return;
}
if (prevHighlightPosition != highlightPosition) {
prevHighlightPosition = highlightPosition;
}
if (index != highlightPosition) {
highlightPosition = index;
notifyItemChanged(prevHighlightPosition);
notifyItemChanged(highlightPosition);
}
}

@Override
public int getItemCount() {
if (media == null) {
return 0;
}

if (media.getTranscript() == null) {
return 0;
}
return media.getTranscript().getSegmentCount();
}

@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
EventBus.getDefault().register(this);
}

@Override
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
super.onDetachedFromRecyclerView(recyclerView);
EventBus.getDefault().unregister(this);
}


public interface SegmentClickListener {
void onTranscriptClicked(int position, TranscriptSegment seg);
}
}
Loading

0 comments on commit 1bb3ca4

Please sign in to comment.