Skip to content

Commit 997de45

Browse files
committed
Thumbnails support
1 parent 28937b2 commit 997de45

39 files changed

+1207
-145
lines changed

demo/src/main/AndroidManifest.xml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,18 @@
1313
android:label="@string/app_name"
1414
android:theme="@style/AppTheme"
1515
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
16+
1617
<activity
1718
android:name=".TranscoderActivity"
18-
android:label="@string/app_name">
19+
android:label="Transcoder">
20+
<intent-filter>
21+
<category android:name="android.intent.category.LAUNCHER" />
22+
</intent-filter>
23+
</activity>
24+
25+
<activity
26+
android:name=".ThumbnailerActivity"
27+
android:label="Thumbnailer">
1928
<intent-filter>
2029
<action android:name="android.intent.action.MAIN" />
2130
<category android:name="android.intent.category.LAUNCHER" />
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package com.otaliastudios.transcoder.demo;
2+
3+
import android.annotation.SuppressLint;
4+
import android.content.ClipData;
5+
import android.content.Intent;
6+
import android.graphics.Color;
7+
import android.net.Uri;
8+
import android.os.Bundle;
9+
import android.os.SystemClock;
10+
import android.text.Editable;
11+
import android.text.TextWatcher;
12+
import android.util.TypedValue;
13+
import android.view.ViewGroup;
14+
import android.widget.EditText;
15+
import android.widget.ImageView;
16+
import android.widget.ProgressBar;
17+
import android.widget.RadioGroup;
18+
import android.widget.TextView;
19+
import android.widget.Toast;
20+
21+
import androidx.annotation.NonNull;
22+
import androidx.annotation.Nullable;
23+
import androidx.appcompat.app.AppCompatActivity;
24+
25+
import com.otaliastudios.transcoder.ThumbnailerListener;
26+
import com.otaliastudios.transcoder.ThumbnailerOptions;
27+
import com.otaliastudios.transcoder.internal.utils.Logger;
28+
import com.otaliastudios.transcoder.resize.AspectRatioResizer;
29+
import com.otaliastudios.transcoder.resize.FractionResizer;
30+
import com.otaliastudios.transcoder.resize.MultiResizer;
31+
import com.otaliastudios.transcoder.resize.PassThroughResizer;
32+
import com.otaliastudios.transcoder.source.DataSource;
33+
import com.otaliastudios.transcoder.source.TrimDataSource;
34+
import com.otaliastudios.transcoder.source.UriDataSource;
35+
import com.otaliastudios.transcoder.thumbnail.Thumbnail;
36+
import com.otaliastudios.transcoder.thumbnail.UniformThumbnailRequest;
37+
38+
import org.jetbrains.annotations.NotNull;
39+
40+
import java.util.ArrayList;
41+
import java.util.List;
42+
import java.util.concurrent.Future;
43+
import java.util.function.Consumer;
44+
45+
import kotlin.collections.ArraysKt;
46+
import kotlin.jvm.functions.Function1;
47+
48+
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
49+
50+
51+
public class ThumbnailerActivity extends AppCompatActivity implements
52+
ThumbnailerListener {
53+
54+
private static final Logger LOG = new Logger("TranscoderActivity");
55+
56+
private static final int REQUEST_CODE_PICK = 1;
57+
private static final int PROGRESS_BAR_MAX = 1000;
58+
59+
private RadioGroup mVideoResolutionGroup;
60+
private RadioGroup mVideoAspectGroup;
61+
private RadioGroup mVideoRotationGroup;
62+
63+
private ProgressBar mProgressView;
64+
private TextView mButtonView;
65+
private EditText mTrimStartView;
66+
private EditText mTrimEndView;
67+
private ViewGroup mThumbnailsView;
68+
69+
private boolean mIsTranscoding;
70+
private Future<Void> mTranscodeFuture;
71+
72+
@SuppressLint("SetTextI18n")
73+
@Override
74+
protected void onCreate(Bundle savedInstanceState) {
75+
super.onCreate(savedInstanceState);
76+
Logger.setLogLevel(Logger.LEVEL_VERBOSE);
77+
setContentView(R.layout.activity_thumbnailer);
78+
79+
mThumbnailsView = findViewById(R.id.thumbnails);
80+
mButtonView = findViewById(R.id.button);
81+
mButtonView.setOnClickListener(v -> {
82+
if (!mIsTranscoding) {
83+
startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT)
84+
.setType("video/*")
85+
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true), REQUEST_CODE_PICK);
86+
} else {
87+
mTranscodeFuture.cancel(true);
88+
}
89+
});
90+
91+
mProgressView = findViewById(R.id.progress);
92+
mTrimStartView = findViewById(R.id.trim_start);
93+
mTrimEndView = findViewById(R.id.trim_end);
94+
mVideoResolutionGroup = findViewById(R.id.resolution);
95+
mVideoAspectGroup = findViewById(R.id.aspect);
96+
mVideoRotationGroup = findViewById(R.id.rotation);
97+
setIsTranscoding(false, null);
98+
}
99+
100+
@Override
101+
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
102+
super.onActivityResult(requestCode, resultCode, data);
103+
if (requestCode == REQUEST_CODE_PICK
104+
&& resultCode == RESULT_OK
105+
&& data != null) {
106+
if (data.getData() != null) {
107+
thumbnails(data.getData());
108+
} else if (data.getClipData() != null) {
109+
ClipData clipData = data.getClipData();
110+
List<Uri> uris = new ArrayList<>();
111+
for (int i = 0; i < clipData.getItemCount(); i++) {
112+
uris.add(clipData.getItemAt(i).getUri());
113+
}
114+
thumbnails(uris.toArray(new Uri[0]));
115+
}
116+
}
117+
}
118+
119+
private void thumbnails(@NonNull Uri... uris) {
120+
LOG.e("Building sources...");
121+
List<DataSource> sources = ArraysKt.map(uris, uri -> new UriDataSource(this, uri));
122+
long trimStart = 0, trimEnd = 0;
123+
try {
124+
trimStart = Long.parseLong(mTrimStartView.getText().toString());
125+
} catch (NumberFormatException e) {
126+
LOG.w("Failed to read trimStart value.", e);
127+
}
128+
try {
129+
trimEnd = Long.parseLong(mTrimEndView.getText().toString());
130+
} catch (NumberFormatException e) {
131+
LOG.w("Failed to read trimEnd value.", e);
132+
}
133+
trimStart = Math.max(0, trimStart) * 1000000;
134+
trimEnd = Math.max(0, trimEnd) * 1000000;
135+
sources.set(0, new TrimDataSource(sources.get(0), trimStart, trimEnd));
136+
137+
LOG.e("Building options...");
138+
ThumbnailerOptions.Builder builder = new ThumbnailerOptions.Builder();
139+
builder.addThumbnailRequest(new UniformThumbnailRequest(8));
140+
builder.setListener(this);
141+
for (DataSource source : sources) {
142+
builder.addDataSource(source);
143+
}
144+
145+
float aspectRatio;
146+
switch (mVideoAspectGroup.getCheckedRadioButtonId()) {
147+
case R.id.aspect_169: aspectRatio = 16F / 9F; break;
148+
case R.id.aspect_43: aspectRatio = 4F / 3F; break;
149+
case R.id.aspect_square: aspectRatio = 1F; break;
150+
default: aspectRatio = 0F;
151+
}
152+
if (aspectRatio > 0) {
153+
builder.addResizer(new AspectRatioResizer(aspectRatio));
154+
}
155+
float fraction;
156+
switch (mVideoResolutionGroup.getCheckedRadioButtonId()) {
157+
case R.id.resolution_half: fraction = 0.5F; break;
158+
case R.id.resolution_third: fraction = 1F / 3F; break;
159+
default: fraction = 1F;
160+
}
161+
builder.addResizer(new FractionResizer(fraction));
162+
int rotation;
163+
switch (mVideoRotationGroup.getCheckedRadioButtonId()) {
164+
case R.id.rotation_90: rotation = 90; break;
165+
case R.id.rotation_180: rotation = 180; break;
166+
case R.id.rotation_270: rotation = 270; break;
167+
default: rotation = 0;
168+
}
169+
builder.setRotation(rotation);
170+
171+
// Launch the transcoding operation.
172+
LOG.e("Starting transcoding!");
173+
setIsTranscoding(true, null);
174+
mTranscodeFuture = builder.thumbnails();
175+
}
176+
177+
@Override
178+
public void onThumbnail(@NotNull Thumbnail thumbnail) {
179+
float size = TypedValue.applyDimension(COMPLEX_UNIT_DIP, 96, getResources().getDisplayMetrics());
180+
ImageView view = new ImageView(this);
181+
view.setLayoutParams(new ViewGroup.LayoutParams((int) size, (int) size));
182+
view.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
183+
view.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
184+
view.setImageBitmap(thumbnail.getBitmap());
185+
mThumbnailsView.addView(view);
186+
double progress = (float) mThumbnailsView.getChildCount() / 8;
187+
mProgressView.setIndeterminate(false);
188+
mProgressView.setProgress((int) Math.round(progress * PROGRESS_BAR_MAX));
189+
}
190+
191+
@Override
192+
public void onThumbnailsCanceled() {
193+
setIsTranscoding(false, "Operation canceled.");
194+
}
195+
196+
@Override
197+
public void onThumbnailsFailed(@NotNull Throwable exception) {
198+
setIsTranscoding(false, "Error occurred. " + exception.getMessage());
199+
}
200+
201+
@Override
202+
public void onThumbnailsCompleted(@NotNull List<Thumbnail> thumbnails) {
203+
setIsTranscoding(false, "Extracted " + thumbnails.size() + " thumbnails.");
204+
}
205+
206+
private void setIsTranscoding(boolean isTranscoding, @Nullable String message) {
207+
mProgressView.setMax(PROGRESS_BAR_MAX);
208+
mProgressView.setProgress(0);
209+
if (isTranscoding) {
210+
mThumbnailsView.removeAllViews();
211+
}
212+
if (message != null) {
213+
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
214+
}
215+
mIsTranscoding = isTranscoding;
216+
mButtonView.setText(mIsTranscoding ? "Cancel Thumbnails" : "Select Videos & Transcode");
217+
}
218+
}

demo/src/main/java/com/otaliastudios/transcoder/demo/TranscoderActivity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
public class TranscoderActivity extends AppCompatActivity implements
4747
TranscoderListener {
4848

49-
private static final Logger LOG = new Logger("DemoApp");
49+
private static final Logger LOG = new Logger("TranscoderActivity");
5050

5151
private static final String FILE_PROVIDER_AUTHORITY = "com.otaliastudios.transcoder.demo.fileprovider";
5252
private static final int REQUEST_CODE_PICK = 1;

0 commit comments

Comments
 (0)