Skip to content

Commit 90504fc

Browse files
authored
Merge pull request #358 from wordpress-mobile/issue/20-video-support
Video support
2 parents c981e53 + 8691cf9 commit 90504fc

File tree

17 files changed

+522
-192
lines changed

17 files changed

+522
-192
lines changed

app/src/main/kotlin/org/wordpress/aztec/demo/MainActivity.kt

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import android.content.pm.PackageManager
88
import android.content.res.Configuration
99
import android.graphics.Bitmap
1010
import android.graphics.BitmapFactory
11+
import android.graphics.Canvas
1112
import android.graphics.drawable.BitmapDrawable
1213
import android.graphics.drawable.ColorDrawable
14+
import android.graphics.drawable.Drawable
1315
import android.net.Uri
1416
import android.os.Build
1517
import android.os.Bundle
@@ -31,16 +33,20 @@ import org.wordpress.android.util.ToastUtils
3133
import org.wordpress.aztec.AztecAttributes
3234
import org.wordpress.aztec.AztecText
3335
import org.wordpress.aztec.HistoryListener
36+
import org.wordpress.aztec.Html
37+
import org.wordpress.aztec.glideloader.GlideVideoThumbnailLoader
3438
import org.wordpress.aztec.picassoloader.PicassoImageLoader
3539
import org.wordpress.aztec.source.SourceViewEditText
40+
import org.wordpress.aztec.spans.AztecMediaSpan
3641
import org.wordpress.aztec.toolbar.AztecToolbar
3742
import org.wordpress.aztec.toolbar.AztecToolbarClickListener
3843
import org.xml.sax.Attributes
3944
import java.io.File
4045

4146
class MainActivity : AppCompatActivity(),
4247
AztecText.OnImeBackListener,
43-
AztecText.OnMediaTappedListener,
48+
AztecText.OnImageTappedListener,
49+
AztecText.OnVideoTappedListener,
4450
AztecToolbarClickListener,
4551
HistoryListener,
4652
OnRequestPermissionsResultCallback,
@@ -98,6 +104,8 @@ class MainActivity : AppCompatActivity(),
98104

99105
private val LONG_TEXT = "<br><br>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "
100106

107+
private val VIDEO = "<video src=\"https://www.w3schools.com/html/mov_bbb.mp4\" />"
108+
101109
private val EXAMPLE =
102110
IMG +
103111
HEADING +
@@ -118,7 +126,8 @@ class MainActivity : AppCompatActivity(),
118126
CODE +
119127
UNKNOWN +
120128
EMOJI +
121-
LONG_TEXT
129+
LONG_TEXT +
130+
VIDEO
122131

123132
private val isRunningTest : Boolean by lazy {
124133
try {
@@ -167,8 +176,8 @@ class MainActivity : AppCompatActivity(),
167176
val options = BitmapFactory.Options()
168177
options.inDensity = DisplayMetrics.DENSITY_DEFAULT
169178
bitmap = BitmapFactory.decodeFile(mediaPath, options)
170-
}
171-
REQUEST_MEDIA_CAMERA_VIDEO -> {
179+
180+
insertImageAndSimulateUpload(bitmap, mediaPath)
172181
}
173182
REQUEST_MEDIA_PHOTO -> {
174183
mediaPath = data?.data.toString()
@@ -178,27 +187,68 @@ class MainActivity : AppCompatActivity(),
178187
val options = BitmapFactory.Options()
179188
options.inDensity = DisplayMetrics.DENSITY_DEFAULT
180189
bitmap = BitmapFactory.decodeStream(stream, null, options)
190+
191+
insertImageAndSimulateUpload(bitmap, mediaPath)
192+
}
193+
REQUEST_MEDIA_CAMERA_VIDEO -> {
194+
mediaPath = data?.data.toString()
181195
}
182196
REQUEST_MEDIA_VIDEO -> {
197+
mediaPath = data?.data.toString()
198+
199+
aztec.videoThumbnailGetter?.loadVideoThumbnail(mediaPath, object : Html.VideoThumbnailGetter.Callbacks {
200+
override fun onThumbnailFailed() {
201+
}
202+
203+
override fun onThumbnailLoaded(drawable: Drawable?) {
204+
val conf = Bitmap.Config.ARGB_8888 // see other conf types
205+
bitmap = Bitmap.createBitmap(drawable!!.intrinsicWidth, drawable.intrinsicHeight, conf)
206+
val canvas = Canvas(bitmap)
207+
drawable.setBounds(0, 0, canvas.width, canvas.height)
208+
drawable.draw(canvas)
209+
210+
insertVideoAndSimulateUpload(bitmap, mediaPath)
211+
}
212+
213+
override fun onThumbnailLoading(drawable: Drawable?) {
214+
}
215+
216+
}, this.resources.displayMetrics.widthPixels)
183217
}
184218
}
185-
186-
insertMediaAndSimulateUpload(bitmap, mediaPath)
187219
}
188220

189221
super.onActivityResult(requestCode, resultCode, data)
190222
}
191223

192-
fun insertMediaAndSimulateUpload(bitmap: Bitmap?, mediaPath: String) {
224+
fun insertImageAndSimulateUpload(bitmap: Bitmap?, mediaPath: String) {
225+
val (id, attrs) = generateAttributesForMedia(mediaPath, isVideo = false)
226+
val mediaSpan = aztec.insertImage(BitmapDrawable(resources, bitmap), attrs)
227+
insertMediaAndSimulateUpload(id, attrs, mediaSpan)
228+
}
229+
230+
fun insertVideoAndSimulateUpload(bitmap: Bitmap?, mediaPath: String) {
231+
val (id, attrs) = generateAttributesForMedia(mediaPath, isVideo = true)
232+
val mediaSpan = aztec.insertVideo(BitmapDrawable(resources, bitmap), attrs)
233+
insertMediaAndSimulateUpload(id, attrs, mediaSpan)
234+
}
235+
236+
private fun generateAttributesForMedia(mediaPath: String, isVideo: Boolean): Pair<String, AztecAttributes> {
193237
val id = (Math.random() * Int.MAX_VALUE).toString()
194238

195239
val attrs = AztecAttributes()
196240
attrs.setValue("src", mediaPath) // Temporary source value. Replace with URL after uploaded.
197241
attrs.setValue("id", id)
198242
attrs.setValue("uploading", "true")
199243

200-
val mediaSpan = aztec.insertMedia(BitmapDrawable(resources, bitmap), attrs)
244+
if (isVideo) {
245+
attrs.setValue("video", "true")
246+
}
247+
248+
return Pair(id, attrs)
249+
}
201250

251+
private fun insertMediaAndSimulateUpload(id: String, attrs: AztecAttributes, mediaSpan: AztecMediaSpan) {
202252
val predicate = object : AztecText.AttributePredicate {
203253
override fun matches(attrs: Attributes): Boolean {
204254
return attrs.getValue("id") == id
@@ -227,6 +277,12 @@ class MainActivity : AppCompatActivity(),
227277
if (progress >= 10000) {
228278
attrs.removeAttribute(attrs.getIndex("uploading"))
229279
aztec.clearOverlays(predicate)
280+
281+
if (attrs.hasAttribute("video")) {
282+
attrs.removeAttribute(attrs.getIndex("video"))
283+
aztec.setOverlay(predicate, 0, ContextCompat.getDrawable(this, android.R.drawable.ic_media_play), Gravity.CENTER)
284+
}
285+
230286
aztec.updateElementAttributes(predicate, attrs)
231287
}
232288
}
@@ -253,9 +309,10 @@ class MainActivity : AppCompatActivity(),
253309
aztec = findViewById(R.id.aztec) as AztecText
254310

255311
aztec.imageGetter = PicassoImageLoader(this, aztec)
256-
// aztec.imageGetter = GlideImageLoader(this)
312+
aztec.videoThumbnailGetter = GlideVideoThumbnailLoader(this)
257313

258-
aztec.setOnMediaTappedListener(this)
314+
aztec.setOnImageTappedListener(this)
315+
aztec.setOnVideoTappedListener(this)
259316

260317
source = findViewById(R.id.source) as SourceViewEditText
261318

@@ -628,15 +685,15 @@ class MainActivity : AppCompatActivity(),
628685
item?.isChecked = (item?.isChecked == false)
629686

630687
when (item?.itemId) {
631-
org.wordpress.aztec.R.id.gallery -> {
688+
R.id.gallery -> {
632689
onGalleryMediaOptionSelected()
633690
return true
634691
}
635-
org.wordpress.aztec.R.id.photo -> {
692+
R.id.photo -> {
636693
showPhotoMediaDialog()
637694
return true
638695
}
639-
org.wordpress.aztec.R.id.video -> {
696+
R.id.video -> {
640697
showVideoMediaDialog()
641698
return true
642699
}
@@ -706,7 +763,21 @@ class MainActivity : AppCompatActivity(),
706763
addVideoMediaDialog!!.show()
707764
}
708765

709-
override fun mediaTapped(attrs: AztecAttributes, naturalWidth: Int, naturalHeight: Int) {
710-
ToastUtils.showToast(this, "Media tapped!")
766+
override fun onImageTapped(attrs: AztecAttributes, naturalWidth: Int, naturalHeight: Int) {
767+
ToastUtils.showToast(this, "Image tapped!")
768+
}
769+
770+
override fun onVideoTapped(attrs: AztecAttributes) {
771+
val url = attrs.getValue("src")
772+
url?.let {
773+
try {
774+
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
775+
intent.setDataAndType(Uri.parse(url), "video/*")
776+
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
777+
startActivity(intent)
778+
} catch (e: Exception) {
779+
ToastUtils.showToast(this, "Video tapped!")
780+
}
781+
}
711782
}
712783
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313

1414
<item
1515
android:id="@+id/video"
16-
android:title="@string/media_popup_video"
17-
android:visible="false" >
16+
android:title="@string/media_popup_video" >
1817
</item>
1918

2019
</menu>

aztec/src/main/java/org/wordpress/aztec/Html.java

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@
3333

3434
import org.ccil.cowan.tagsoup.HTMLSchema;
3535
import org.ccil.cowan.tagsoup.Parser;
36-
import org.wordpress.aztec.AztecText.OnMediaTappedListener;
36+
import org.wordpress.aztec.AztecText.OnImageTappedListener;
37+
import org.wordpress.aztec.AztecText.OnVideoTappedListener;
3738
import org.wordpress.aztec.spans.AztecBlockSpan;
3839
import org.wordpress.aztec.spans.AztecCodeSpan;
3940
import org.wordpress.aztec.spans.AztecCommentSpan;
4041
import org.wordpress.aztec.spans.AztecCursorSpan;
41-
import org.wordpress.aztec.spans.AztecHeadingSpan;
4242
import org.wordpress.aztec.spans.AztecInlineSpan;
4343
import org.wordpress.aztec.spans.AztecMediaSpan;
4444
import org.wordpress.aztec.spans.AztecRelativeSizeBigSpan;
@@ -89,8 +89,6 @@ public interface ImageGetter {
8989
void loadImage(String source, Html.ImageGetter.Callbacks callbacks, int maxWidth);
9090

9191
interface Callbacks {
92-
void onUseDefaultImage();
93-
9492
void onImageFailed();
9593

9694
void onImageLoaded(Drawable drawable);
@@ -99,6 +97,19 @@ interface Callbacks {
9997
}
10098
}
10199

100+
public interface VideoThumbnailGetter {
101+
102+
void loadVideoThumbnail(String source, Html.VideoThumbnailGetter.Callbacks callbacks, int maxWidth);
103+
104+
interface Callbacks {
105+
void onThumbnailFailed();
106+
107+
void onThumbnailLoaded(Drawable drawable);
108+
109+
void onThumbnailLoading(Drawable drawable);
110+
}
111+
}
112+
102113
/**
103114
* Is notified when HTML tags are encountered that the parser does
104115
* not know how to interpret.
@@ -108,7 +119,7 @@ public interface TagHandler {
108119
* This method will be called whenn the HTML parser encounters
109120
* a tag that it does not know how to interpret.
110121
*/
111-
boolean handleTag(boolean opening, String tag, Editable output, OnMediaTappedListener onMediaTappedListener,
122+
boolean handleTag(boolean opening, String tag, Editable output,
112123
Context context, Attributes attributes, int nestingLevel);
113124
}
114125

@@ -123,9 +134,9 @@ private Html() {
123134
* <p/>
124135
* <p>This uses TagSoup to handle real HTML, including all of the brokenness found in the wild.
125136
*/
126-
public static Spanned fromHtml(String source, OnMediaTappedListener onMediaTappedListener,
137+
public static Spanned fromHtml(String source, OnImageTappedListener onImageTappedListener, OnVideoTappedListener onVideoTappedListener,
127138
UnknownHtmlSpan.OnUnknownHtmlClickListener onUnknownHtmlClickListener, Context context) {
128-
return fromHtml(source, null, onMediaTappedListener, onUnknownHtmlClickListener, context);
139+
return fromHtml(source, null, onUnknownHtmlClickListener, context);
129140
}
130141

131142
/**
@@ -146,8 +157,10 @@ private static class HtmlParser {
146157
* <p/>
147158
* <p>This uses TagSoup to handle real HTML, including all of the brokenness found in the wild.
148159
*/
149-
public static Spanned fromHtml(String source, TagHandler tagHandler, OnMediaTappedListener onMediaTappedListener,
150-
UnknownHtmlSpan.OnUnknownHtmlClickListener onUnknownHtmlClickListener, Context context) {
160+
public static Spanned fromHtml(String source, TagHandler tagHandler,
161+
UnknownHtmlSpan.OnUnknownHtmlClickListener onUnknownHtmlClickListener,
162+
Context context) {
163+
151164
Parser parser = new Parser();
152165
try {
153166
parser.setProperty(Parser.schemaProperty, HtmlParser.schema);
@@ -161,8 +174,8 @@ public static Spanned fromHtml(String source, TagHandler tagHandler, OnMediaTapp
161174
}
162175

163176
HtmlToSpannedConverter converter =
164-
new HtmlToSpannedConverter(source, tagHandler,
165-
parser, onMediaTappedListener, onUnknownHtmlClickListener, context);
177+
new HtmlToSpannedConverter(source, tagHandler, parser, onUnknownHtmlClickListener, context);
178+
166179
return converter.convert();
167180
}
168181

@@ -193,18 +206,15 @@ class HtmlToSpannedConverter implements ContentHandler, LexicalHandler {
193206
private SpannableStringBuilder spannableStringBuilder;
194207
private Html.TagHandler tagHandler;
195208
private Context context;
196-
private OnMediaTappedListener onMediaTappedListener;
197209

198210
public HtmlToSpannedConverter(
199211
String source, Html.TagHandler tagHandler,
200-
Parser parser, OnMediaTappedListener onMediaTappedListener,
201-
UnknownHtmlSpan.OnUnknownHtmlClickListener onUnknownHtmlClickListener, Context context) {
212+
Parser parser, UnknownHtmlSpan.OnUnknownHtmlClickListener onUnknownHtmlClickListener, Context context) {
202213
mSource = source;
203214
spannableStringBuilder = new SpannableStringBuilder();
204215
this.tagHandler = tagHandler;
205216
mReader = parser;
206217
this.context = context;
207-
this.onMediaTappedListener = onMediaTappedListener;
208218
this.onUnknownHtmlClickListener = onUnknownHtmlClickListener;
209219
}
210220

@@ -224,7 +234,7 @@ public Spanned convert() {
224234
// Fix flags and range for paragraph-type markup.
225235
Object[] paragraphs = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), ParagraphStyle.class);
226236
for (Object paragraph : paragraphs) {
227-
if (paragraph instanceof UnknownHtmlSpan || paragraph instanceof AztecBlockSpan || paragraph instanceof AztecMediaSpan || paragraph instanceof AztecHeadingSpan) {
237+
if (paragraph instanceof UnknownHtmlSpan || paragraph instanceof AztecBlockSpan || paragraph instanceof AztecMediaSpan) {
228238
continue;
229239
}
230240
int start = spannableStringBuilder.getSpanStart(paragraph);
@@ -302,8 +312,9 @@ private void handleStartTag(String tag, Attributes attributes, int nestingLevel)
302312
insidePreTag = true;
303313
}
304314

305-
boolean tagHandled = tagHandler.handleTag(true, tag, spannableStringBuilder, onMediaTappedListener,
315+
boolean tagHandled = tagHandler.handleTag(true, tag, spannableStringBuilder,
306316
context, attributes, nestingLevel);
317+
307318
if (tagHandled) {
308319
return;
309320
}
@@ -380,8 +391,8 @@ private void handleEndTag(String tag, int nestingLevel) {
380391
if (tag.equalsIgnoreCase("pre")) {
381392
insidePreTag = false;
382393
}
383-
tagHandler.handleTag(false, tag, spannableStringBuilder, onMediaTappedListener, context, new AztecAttributes(),
384-
nestingLevel);
394+
tagHandler.handleTag(false, tag, spannableStringBuilder, context,
395+
new AztecAttributes(), nestingLevel);
385396
}
386397
}
387398

aztec/src/main/kotlin/org/wordpress/aztec/AztecParser.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ package org.wordpress.aztec
2121
import android.content.Context
2222
import android.text.*
2323
import android.text.style.CharacterStyle
24-
import org.wordpress.aztec.AztecText.OnMediaTappedListener
24+
import org.wordpress.aztec.AztecText.OnImageTappedListener
25+
import org.wordpress.aztec.AztecText.OnVideoTappedListener
2526
import org.wordpress.aztec.spans.*
2627
import org.wordpress.aztec.util.SpanWrapper
2728
import java.util.*
@@ -34,12 +35,12 @@ class AztecParser {
3435
internal var hiddenSpans: IntArray = IntArray(0)
3536
internal var spanCursorPosition = -1
3637

37-
fun fromHtml(source: String, onMediaTappedListener: OnMediaTappedListener?,
38-
onUnknownHtmlClickListener: UnknownHtmlSpan.OnUnknownHtmlClickListener?, context: Context): Spanned {
38+
fun fromHtml(source: String, onImageTappedListener: OnImageTappedListener?,
39+
onVideoTappedListener: OnVideoTappedListener?, onUnknownHtmlClickListener: UnknownHtmlSpan.OnUnknownHtmlClickListener?, context: Context): Spanned {
3940
val tidySource = tidy(source)
4041

4142
val spanned = SpannableStringBuilder(Html.fromHtml(tidySource, AztecTagHandler(),
42-
onMediaTappedListener, onUnknownHtmlClickListener, context))
43+
onUnknownHtmlClickListener, context))
4344

4445
addVisualNewlinesToBlockElements(spanned)
4546
markBlockElementsAsParagraphs(spanned)

0 commit comments

Comments
 (0)