Added embedded audio player

pull/214/head
M66B 11 months ago
parent 0474a73b4a
commit 1089513888

@ -28,9 +28,13 @@ import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.pdf.PdfRenderer;
import android.media.AudioAttributes;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
@ -90,12 +94,24 @@ public class AdapterMedia extends RecyclerView.Adapter<AdapterMedia.ViewHolder>
view.setOnLongClickListener(null);
}
private void showPlayerState(EntityAttachment attachment) {
if (attachment.player == null)
ivImage.setImageResource(R.drawable.twotone_play_arrow_48);
else
ivImage.setImageResource(R.drawable.twotone_stop_48);
}
private void bindTo(EntityAttachment attachment) {
tvCaption.setText(attachment.name);
tvCaption.setVisibility(TextUtils.isEmpty(attachment.name) ? View.GONE : View.VISIBLE);
tvProperties.setVisibility(View.GONE);
if (attachment.available) {
if (attachment.isAudio()) {
showPlayerState(attachment);
return;
}
Bundle args = new Bundle();
args.putSerializable("file", attachment.getFile(context));
args.putString("type", attachment.getMimeType());
@ -243,13 +259,71 @@ public class AdapterMedia extends RecyclerView.Adapter<AdapterMedia.ViewHolder>
return;
EntityAttachment attachment = items.get(pos);
if (attachment.available)
try {
Helper.share(context, attachment.getFile(context), attachment.getMimeType(), attachment.name);
} catch (Throwable ex) {
Log.unexpectedError(parentFragment.getParentFragmentManager(), ex);
if (attachment.available) {
if (attachment.isAudio()) {
try {
if (attachment.player == null) {
final Runnable updateTime = new Runnable() {
@Override
public void run() {
MediaPlayer player = attachment.player;
tvProperties.setVisibility(player == null ? View.GONE : View.VISIBLE);
if (player != null) {
String pos = Helper.formatDuration(player.getCurrentPosition(), false);
String duration = Helper.formatDuration(player.getDuration());
tvProperties.setText(pos + " / " + duration);
view.postDelayed(this, 1000L);
}
}
};
Uri uri = FileProviderEx.getUri(context, BuildConfig.APPLICATION_ID, attachment.getFile(context), attachment.name);
attachment.player = new MediaPlayer();
attachment.player.setAudioAttributes(
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributes.USAGE_MEDIA)
.build()
);
attachment.player.setDataSource(context, uri);
attachment.player.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
attachment.player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
// https://issuetracker.google.com/issues/36921987
if (BuildConfig.DEBUG)
view.postDelayed(updateTime, 500L);
}
});
attachment.player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
attachment.player = null;
showPlayerState(attachment);
}
});
attachment.player.prepareAsync();
} else {
attachment.player.stop();
attachment.player = null;
}
showPlayerState(attachment);
} catch (Throwable ex) {
attachment.player = null;
ivImage.setImageResource(R.drawable.twotone_warning_24);
Log.unexpectedError(parentFragment, ex);
}
} else {
try {
Helper.share(context, attachment.getFile(context), attachment.getMimeType(), attachment.name);
} catch (Throwable ex) {
Log.unexpectedError(parentFragment.getParentFragmentManager(), ex);
}
}
else {
} else {
if (attachment.progress == null) {
Bundle args = new Bundle();
args.putLong("id", attachment.id);
@ -337,6 +411,15 @@ public class AdapterMedia extends RecyclerView.Adapter<AdapterMedia.ViewHolder>
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroyed() {
Log.d(AdapterMedia.this + " parent destroyed");
for (EntityAttachment attachment : items)
if (attachment.player != null)
try {
attachment.player.stop();
} catch (Throwable ex) {
Log.w(ex);
} finally {
attachment.player = null;
}
AdapterMedia.this.parentFragment = null;
owner.getLifecycle().removeObserver(this);
}

@ -302,6 +302,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private int message_zoom;
private boolean attachments_alt;
private boolean thumbnails;
private boolean pdf_preview;
private boolean audio_preview;
private boolean contrast;
private boolean hyphenation;
private String display_font;
@ -311,7 +313,6 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private boolean authentication_indicator;
private boolean infra;
private boolean tld_flags;
private boolean pdf_preview;
private boolean autoclose_unseen;
private boolean collapse_marked;
@ -3686,9 +3687,11 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
if (thumbnails && bind_extras) {
for (EntityAttachment attachment : attachments)
if ((pdf_preview && attachment.isPDF()) ||
(audio_preview && attachment.isAudio()) ||
(attachment.isAttachment() && attachment.isImage())) {
media.add(attachment);
if (attachment.available && !attachment.isPDF())
if (attachment.available &&
attachment.isAttachment() && attachment.isImage())
iavailable++;
}
}
@ -8036,6 +8039,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
this.message_zoom = prefs.getInt("message_zoom", 100);
this.attachments_alt = prefs.getBoolean("attachments_alt", false);
this.thumbnails = prefs.getBoolean("thumbnails", true);
this.pdf_preview = prefs.getBoolean("pdf_preview", true);
this.audio_preview = prefs.getBoolean("audio_preview", false);
this.contrast = prefs.getBoolean("contrast", false);
this.hyphenation = prefs.getBoolean("hyphenation", false);
this.display_font = prefs.getString("display_font", "");
@ -8045,7 +8050,6 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
this.authentication_indicator = prefs.getBoolean("authentication_indicator", false);
this.infra = prefs.getBoolean("infra", false);
this.tld_flags = prefs.getBoolean("tld_flags", false);
this.pdf_preview = prefs.getBoolean("pdf_preview", true);
this.language_detection = prefs.getBoolean("language_detection", false);
this.autoclose_unseen = prefs.getBoolean("autoclose_unseen", false);
this.collapse_marked = prefs.getBoolean("collapse_marked", true);

@ -23,6 +23,7 @@ import static androidx.room.ForeignKey.CASCADE;
import android.content.Context;
import android.content.SharedPreferences;
import android.media.MediaPlayer;
import android.net.Uri;
import android.text.TextUtils;
@ -101,6 +102,9 @@ public class EntityAttachment {
@Ignore
public boolean selected = false;
@Ignore
public MediaPlayer player = null;
// Gmail sends inline images as attachments with a name and cid
boolean isInline() {
@ -116,6 +120,11 @@ public class EntityAttachment {
return ImageHelper.isImage(getMimeType());
}
boolean isAudio() {
String type = getMimeType();
return (type != null && type.startsWith("audio/"));
}
boolean isPDF() {
return "application/pdf".equals(getMimeType());
}

@ -154,7 +154,8 @@ public class FragmentOptions extends FragmentBase {
"font_size_sender", "sender_ellipsize",
"subject_top", "subject_italic", "highlight_subject", "font_size_subject", "subject_ellipsize",
"keywords_header", "labels_header", "flags", "flags_background", "preview", "preview_italic", "preview_lines", "align_header",
"message_zoom", "overview_mode", "addresses", "button_extra", "attachments_alt", "thumbnails",
"message_zoom", "overview_mode", "addresses", "button_extra", "attachments_alt",
"thumbnails", "pdf_preview", "audio_preview",
"contrast", "hyphenation", "display_font", "monospaced_pre",
"list_count", "bundled_fonts", "narrow_fonts", "parse_classes",
"background_color", "text_color", "text_size", "text_font", "text_align", "text_titles", "text_separators",

@ -185,6 +185,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
private SwitchCompat swAttachmentsAlt;
private SwitchCompat swThumbnails;
private SwitchCompat swPdfPreview;
private SwitchCompat swAudioPreview;
private SwitchCompat swListCount;
private SwitchCompat swBundledFonts;
@ -224,7 +225,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
"hyphenation", "display_font", "contrast", "monospaced_pre",
"text_separators",
"collapse_quotes", "image_placeholders", "inline_images", "button_extra",
"unzip", "attachments_alt", "thumbnails", "pdf_preview",
"unzip", "attachments_alt", "thumbnails", "pdf_preview", "audio_preview",
"list_count", "bundled_fonts", "narrow_fonts", "parse_classes",
"background_color", "text_color", "text_size", "text_font", "text_align", "text_titles",
"authentication", "authentication_indicator"
@ -357,6 +358,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
swAttachmentsAlt = view.findViewById(R.id.swAttachmentsAlt);
swThumbnails = view.findViewById(R.id.swThumbnails);
swPdfPreview = view.findViewById(R.id.swPdfPreview);
swAudioPreview = view.findViewById(R.id.swAudioPreview);
swListCount = view.findViewById(R.id.swListCount);
swBundledFonts = view.findViewById(R.id.swBundledFonts);
@ -1318,6 +1320,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("thumbnails", checked).apply();
swPdfPreview.setEnabled(checked);
swAudioPreview.setEnabled(checked);
}
});
@ -1328,6 +1331,13 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
}
});
swAudioPreview.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("audio_preview", checked).apply();
}
});
swListCount.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@ -1671,6 +1681,8 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
swThumbnails.setChecked(prefs.getBoolean("thumbnails", true));
swPdfPreview.setChecked(prefs.getBoolean("pdf_preview", true));
swPdfPreview.setEnabled(swThumbnails.isChecked());
swAudioPreview.setChecked(prefs.getBoolean("audio_preview", false));
swAudioPreview.setEnabled(swThumbnails.isChecked());
swListCount.setChecked(prefs.getBoolean("list_count", false));
swBundledFonts.setChecked(prefs.getBoolean("bundled_fonts", true));

@ -2308,6 +2308,10 @@ public class Helper {
}
static String formatDuration(long ms) {
return formatDuration(ms, true);
}
static String formatDuration(long ms, boolean withFraction) {
int sign = (ms < 0 ? -1 : 1);
ms = Math.abs(ms);
int days = (int) (ms / (24 * 3600 * 1000L));
@ -2317,7 +2321,7 @@ public class Helper {
return (sign < 0 ? "-" : "") +
(days > 0 ? days + " " : "") +
DateUtils.formatElapsedTime(seconds) +
(ms == 0 ? "" : "." + ms);
(ms == 0 || !withFraction ? "" : "." + ms);
}
static String formatNumber(Integer number, long max, NumberFormat nf) {

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M10,8.64v6.72L15.27,12z"
android:strokeAlpha="0.3"
android:fillAlpha="0.3"/>
<path
android:fillColor="@android:color/white"
android:pathData="M8,19l11,-7L8,5v14zM10,8.64L15.27,12 10,15.36L10,8.64z"/>
</vector>

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M8,8h8v8H8z"
android:strokeAlpha="0.3"
android:fillAlpha="0.3"/>
<path
android:fillColor="@android:color/white"
android:pathData="M6,18h12V6H6v12zM8,8h8v8H8V8z"/>
</vector>

@ -2047,6 +2047,18 @@
app:layout_constraintTop_toBottomOf="@id/swThumbnails"
app:switchPadding="12dp" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swAudioPreview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:text="@string/title_advanced_audio_preview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swPdfPreview"
app:switchPadding="12dp" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpUnzip"
android:layout_width="0dp"

@ -637,6 +637,7 @@
<string name="title_advanced_attachments_alt">Show attachments after the message text</string>
<string name="title_advanced_thumbnails">Show image thumbnails after the message text</string>
<string name="title_advanced_pdf_preview">Show PDF thumbnails</string>
<string name="title_advanced_audio_preview">Show embedded audio player</string>
<string name="title_advanced_message_text_zoom2">Default message text zoom: %1$s %%</string>
<string name="title_advanced_editor_text_zoom">Zoom message text also in the message editor</string>
<string name="title_advanced_overview_mode">Zoom original messages to fit the screen</string>

Loading…
Cancel
Save