Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display transcript text and follow along the audio #7103

Merged
merged 58 commits into from May 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
57b4e39
Transcript display using dialog
tonytamsf Apr 4, 2024
7ce7895
spotbug
tonytamsf Apr 17, 2024
3481270
refactor
tonytamsf Apr 17, 2024
a356b1a
remove license file
tonytamsf Apr 17, 2024
a6a9030
handle only a single speaker
tonytamsf Apr 17, 2024
5a85935
address comments 1
tonytamsf Apr 17, 2024
78a6e4e
checkstyle
tonytamsf Apr 17, 2024
1e27245
checkstyle
tonytamsf Apr 17, 2024
5c524a9
address comment 2
tonytamsf Apr 17, 2024
a446807
checkstyle
tonytamsf Apr 17, 2024
f91cb38
checkstyle
tonytamsf Apr 17, 2024
c69b265
checkstyle
tonytamsf Apr 17, 2024
31cd9d2
checkstyle
tonytamsf Apr 18, 2024
d75dae3
unit test changes after changes to 5 second segments
tonytamsf Apr 18, 2024
c17fff6
working using adapter
tonytamsf Apr 18, 2024
22afa76
fix bad rv viewholder when scrolling transcript fast
tonytamsf Apr 18, 2024
6383ceb
force network load of transcript
tonytamsf Apr 18, 2024
fbd00f6
checkstyle
tonytamsf Apr 18, 2024
fba119b
checkstyle
tonytamsf Apr 18, 2024
0b6774e
remove custom setting of background for dialog
tonytamsf Apr 19, 2024
c436f43
revert
tonytamsf Apr 19, 2024
6df781e
review feedback
tonytamsf Apr 19, 2024
299ba0d
checkstyle
tonytamsf Apr 19, 2024
1bb0785
checkstyle
tonytamsf Apr 19, 2024
d0e7d7a
Design tweaks
ByteHamster Apr 19, 2024
a184d6a
checkstyle
tonytamsf Apr 20, 2024
102f469
checkstyle
tonytamsf Apr 20, 2024
1374580
do not begin a line in transcript with a non-alphanumeric character
tonytamsf Apr 20, 2024
c255e95
be more careful about the last json object
tonytamsf Apr 20, 2024
adacf23
checkstyle
tonytamsf Apr 20, 2024
0f4ff71
revert after fixing offline mode
tonytamsf Apr 20, 2024
3ff78c4
Use simple binary search to replace 3 different data structures
ByteHamster Apr 20, 2024
df5899f
Empty-Commit
tonytamsf Apr 21, 2024
f45c339
unit test fixes for transcript parsing
tonytamsf Apr 21, 2024
99bc969
do not start a sentence with non alpha characters
tonytamsf Apr 22, 2024
1d1f857
reverse logic for whether a transcript segment starts with a non alph…
tonytamsf Apr 22, 2024
88e4a66
support follow audio option
tonytamsf Apr 22, 2024
7c9b356
checkstyle
tonytamsf Apr 22, 2024
d5f0385
checkstyle
tonytamsf Apr 22, 2024
8b22dd4
checkstyle
tonytamsf Apr 22, 2024
520332b
checkstyle
tonytamsf Apr 22, 2024
adb90f0
more subtle hiding of the follow audio checkbox
tonytamsf Apr 22, 2024
9262eec
Simplify if condition
ByteHamster Apr 27, 2024
3f77cfb
Backport: Switch Emulator CI to Ubuntu (#7140)
ByteHamster Apr 27, 2024
f934aeb
follow audio using layout
tonytamsf May 11, 2024
14ca190
fix followAudio checkbox overlapping with recyclerview in transcript
tonytamsf May 11, 2024
70fc3bd
fix progress loading
tonytamsf May 11, 2024
3dae14e
fix unit test for transcript
tonytamsf May 11, 2024
e520c0a
uncomment one unit test
tonytamsf May 11, 2024
d8feec8
refactor and move TranscriptUtils
tonytamsf May 11, 2024
c8be6fb
minor UI feedback on transcripts
tonytamsf May 14, 2024
b7e1335
xmllint
tonytamsf May 14, 2024
75a1adb
checkstyle
tonytamsf May 14, 2024
f501f41
Simplify some code
ByteHamster May 14, 2024
3d09a7c
Don't start another network request when chapter loading is interrupted
ByteHamster May 15, 2024
f831924
Scroll more quickly when smooth scrolling after quick scroll
ByteHamster May 15, 2024
7758393
Code simplification
ByteHamster May 15, 2024
4b70862
disable refresh when downloading so we do not repeately hit the button
tonytamsf May 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle
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 Down
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
@@ -0,0 +1,159 @@
package de.danoeh.antennapod.ui.screen.playback;

import androidx.recyclerview.widget.RecyclerView;

import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import de.danoeh.antennapod.playback.service.PlaybackController;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.jsoup.internal.StringUtil;

import de.danoeh.antennapod.databinding.FragmentItemTranscriptRvBinding;
import de.danoeh.antennapod.event.playback.PlaybackPositionEvent;
import de.danoeh.antennapod.model.feed.Transcript;
import de.danoeh.antennapod.model.feed.TranscriptSegment;
import de.danoeh.antennapod.parser.transcript.TranscriptParser;
import de.danoeh.antennapod.playback.base.PlayerStatus;

import java.util.Hashtable;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
* {@link RecyclerView.Adapter} that can display a {@link PlaceholderItem}.
* TODO: Replace the implementation with code for your data type.
*/
public class ItemTranscriptRvAdapter extends RecyclerView.Adapter<ItemTranscriptRvAdapter.ViewHolder> {
tonytamsf marked this conversation as resolved.
Show resolved Hide resolved

public String tag = "ItemTranscriptRVAdapter";
public Hashtable<Long, Integer> positions;
public Hashtable<Integer, TranscriptSegment> snippets;
PlaybackController controller;
tonytamsf marked this conversation as resolved.
Show resolved Hide resolved

private Transcript transcript;

public ItemTranscriptRvAdapter(Transcript t) {
positions = new Hashtable<Long, Integer>();
snippets = new Hashtable<Integer, TranscriptSegment>();
setTranscript(t);
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

return new ViewHolder(FragmentItemTranscriptRvBinding.inflate(LayoutInflater.from(parent.getContext()),
parent,
false));

}

public void setController(PlaybackController controller) {
this.controller = controller;
}

public void setTranscript(Transcript t) {
transcript = t;
if (transcript == null) {
return;
}
TreeMap<Long, TranscriptSegment> segmentsMap = transcript.getSegmentsMap();
tonytamsf marked this conversation as resolved.
Show resolved Hide resolved
Object[] objs = segmentsMap.entrySet().toArray();
for (int i = 0; i < objs.length; i++) {
Map.Entry<Long, TranscriptSegment> seg;
seg = (Map.Entry<Long, TranscriptSegment>) objs[i];
positions.put((Long) seg.getKey(), i);
snippets.put(i, seg.getValue());
}
}

@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
TreeMap<Long, TranscriptSegment> segmentsMap;
SortedMap<Long, TranscriptSegment> map;

segmentsMap = transcript.getSegmentsMap();
// TODO: fix this performance
TreeMap.Entry entry = (TreeMap.Entry) segmentsMap.entrySet().toArray()[position];
TranscriptSegment seg = (TranscriptSegment) entry.getValue();
Long k = (Long) entry.getKey();

Log.d(tag, "onBindViewHolder position " + position + " RV pos " + k);
holder.transcriptSegment = seg;
holder.viewTimecode.setText(TranscriptParser.secondsToTime(k));
holder.viewTimecode.setVisibility(View.GONE);
if (! StringUtil.isBlank(seg.getSpeaker())) {
TreeMap.Entry prevEntry = null;
try {
prevEntry = (TreeMap.Entry) segmentsMap.entrySet().toArray()[position - 1];
} catch (ArrayIndexOutOfBoundsException e) {
Log.d(tag, "ArrayIndexOutOfBoundsException");
}
TranscriptSegment prevSeg = null;
if (prevEntry != null) {
prevSeg = (TranscriptSegment) prevEntry.getValue();
}
if (prevEntry != null && prevSeg.getSpeaker().equals(seg.getSpeaker())) {
holder.viewTimecode.setVisibility(View.GONE);
holder.viewContent.setText(seg.getWords());
} else {
holder.viewTimecode.setText(TranscriptParser.secondsToTime(k) + " " + seg.getSpeaker());
holder.viewContent.setText(seg.getWords());
holder.viewTimecode.setVisibility(View.VISIBLE);
}
} else {
holder.viewContent.setText(seg.getWords());
}
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(PlaybackPositionEvent event) {
Log.d(tag, "onEventMainThread ItemTranscriptRVAdapter");
}

@Override
public int getItemCount() {
if (transcript == null) {
return 0;
}
return transcript.getSegmentsMap().size();
}

public class ViewHolder extends RecyclerView.ViewHolder {
tonytamsf marked this conversation as resolved.
Show resolved Hide resolved
public final TextView viewTimecode;
public final TextView viewContent;
public TranscriptSegment transcriptSegment;

public ViewHolder(FragmentItemTranscriptRvBinding binding) {
super(binding.getRoot());
viewTimecode = binding.speaker;
viewContent = binding.content;
viewContent.setOnClickListener(v -> {
Log.d(tag, "Clicked on " + transcriptSegment.getWords());
long startTime = transcriptSegment.getStartTime();
long endTime = transcriptSegment.getEndTime();
if (! (controller.getPosition() >= startTime
&& controller.getPosition() <= endTime)) {
controller.seekTo((int) startTime);

if (controller.getStatus() == PlayerStatus.PAUSED
|| controller.getStatus() == PlayerStatus.STOPPED) {
controller.playPause();
}
} else {
controller.playPause();
}
});
}

@Override
public String toString() {
return super.toString() + " '" + viewContent.getText() + "'";
}
}
}