Skip to content

Commit

Permalink
Merge pull request #30 from kifio/improvments
Browse files Browse the repository at this point in the history
Fix lifecycle issue, leads to crash in VIdeoSurfaceView and add saving video to sdcard.
  • Loading branch information
krazykira committed Apr 8, 2020
2 parents efa2c0f + 4270c36 commit 1927979
Show file tree
Hide file tree
Showing 73 changed files with 2,016 additions and 491 deletions.
7 changes: 6 additions & 1 deletion README.md
@@ -1,5 +1,4 @@
# VidEffects
[![Codewake](https://www.codewake.com/badges/ask_question_flat_square.svg)](https://www.codewake.com/p/videffects)
[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-VidEffects-green.svg?style=true)](https://android-arsenal.com/details/1/4029)

This is an Android library which can be used to apply different Filters/Effects on videos. It uses vertexShaders and fragmentShaders to apply effects on `GLSurfaceView`. It uses `MediaPlayer` instance for playing videos on `GlSurfaceView`. See the sample app in order to see a working demo.
Expand Down Expand Up @@ -36,6 +35,7 @@ The following list of effects are currently avaialble and can be applied using V

The effects applied using this library are temporary. What that means is that the orignal video doesn't change. Effects are only applied during video playback and once the video ends the effects end with it. In the future, i am aiming to apply permanant effect to videos. You guys are welcome to help out using PRs.
<br>For now, if you are really desperate and want to apply effects then you can use [FFmpeg](https://ffmpeg.org/) to apply effects on videos. Details about how to do that can be seen on this [wiki page](https://github.com/krazykira/VidEffects/wiki/Permanent-video-effects)
Saving video available only for Filters and requires Android 23 min api version.

## How to use it
- Add the following code to your project's `build.gradle` file
Expand Down Expand Up @@ -119,7 +119,12 @@ dependencies {
![Video screenshot with Invert Colors Effect](https://cloud.githubusercontent.com/assets/2201511/9244236/ea09d344-41b2-11e5-9e71-f04601fd61e9.png)
## Special Thanks to
* [Sisik](https://github.com/sixo) blog posts about converting video and audio files with MediaCodec and Muxer.
https://sisik.eu/blog/android/media/video-to-grayscale
https://sisik.eu/blog/android/media/mix-audio-into-video
* This blog [post](http://code.tutsplus.com/tutorials/how-to-use-android-media-effects-with-opengl-es--cms-23650) by Ashraff Hathibelagal for easing the process of learning Opengl and explaining how to apply effects on images.
* [GrepCode](http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.0.1_r1/android/filterpacks/imageproc/package-info.java) For providing me with Android source code which were helpful in writing video effects/filters.
* [MediaPlayerSurface by crossle](https://github.com/crossle/MediaPlayerSurface) It helped me in playing video using a GlsurfaceView.
Expand Down
Binary file added app/.DS_Store
Binary file not shown.
21 changes: 14 additions & 7 deletions app/build.gradle
@@ -1,13 +1,19 @@
apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 24
buildToolsVersion "23.0.3"
compileSdkVersion 29
buildToolsVersion "29.0.3"

defaultConfig {
applicationId "com.sherazkhilji.sample"
minSdkVersion 16
targetSdkVersion 24
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
}

buildTypes {
Expand All @@ -19,7 +25,8 @@ android {
}

dependencies {
compile 'com.sherazkhilji.videffects:videffects:1.0.2'
// compile project(":videffects")

implementation project(":videffects")
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'com.google.android.material:material:1.1.0'
}
Binary file added app/src/.DS_Store
Binary file not shown.
Binary file added app/src/main/.DS_Store
Binary file not shown.
35 changes: 25 additions & 10 deletions app/src/main/AndroidManifest.xml
@@ -1,32 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sherazkhilji.sample"
android:versionCode="1"
android:versionName="1.0">
xmlns:tools="http://schemas.android.com/tools"
package="com.sherazkhilji.sample"
tools:ignore="GoogleAppIndexingWarning">

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-feature
android:glEsVersion="0x00020000"
android:required="true"/>
android:required="true" />

<application
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
android:theme="@style/Theme.AppCompat">
<activity
android:name="com.videffects.sample.SamplePlayerActivity"
android:name="com.videffects.sample.view.SamplePlayerActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:label="@string/app_name"
android:screenOrientation="landscape">
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.MAIN"/>-->
<!-- <category android:name="android.intent.category.LAUNCHER"/>-->
<!-- </intent-filter>-->
</activity>

<activity
android:name="com.videffects.sample.view.AssetsGalleryActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>


<activity
android:name="com.videffects.sample.view.VideoActivity"
android:screenOrientation="portrait" />

</application>

</manifest>
Binary file added app/src/main/assets/.DS_Store
Binary file not shown.
Binary file added app/src/main/assets/sample.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/src/main/assets/video_0.mp4
Binary file not shown.
Binary file added app/src/main/assets/video_0.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/src/main/assets/video_2.mp4
Binary file not shown.
Binary file added app/src/main/assets/video_2.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
167 changes: 167 additions & 0 deletions app/src/main/java/com/videffects/sample/controller/VideoController.kt
@@ -0,0 +1,167 @@
package com.videffects.sample.controller

import android.content.res.AssetFileDescriptor
import android.media.MediaPlayer
import android.media.MediaPlayer.MEDIA_ERROR_UNKNOWN
import android.os.Build
import android.os.Environment
import android.util.Log
import android.widget.SeekBar
import android.widget.Toast
import androidx.annotation.RequiresApi
import com.sherazkhilji.videffects.filter.AutoFixFilter
import com.sherazkhilji.videffects.filter.GrainFilter
import com.sherazkhilji.videffects.filter.HueFilter
import com.sherazkhilji.videffects.filter.NoEffectFilter
import com.sherazkhilji.videffects.interfaces.ConvertResultListener
import com.sherazkhilji.videffects.interfaces.Filter
import com.sherazkhilji.videffects.interfaces.ShaderInterface
import com.sherazkhilji.videffects.model.Metadata
import com.videffects.sample.interfaces.OnSelectShaderListener
import com.videffects.sample.interfaces.ProgressChangeListener
import com.videffects.sample.model.*
import com.videffects.sample.view.ShaderChooserDialog
import com.videffects.sample.view.VideoActivity
import java.io.File

class VideoController(private var activity: VideoActivity?,
filename: String) {

companion object {
private const val TAG = "kVideoController"
}

private var filter: Filter = NoEffectFilter()

private var assetFileDescriptor: AssetFileDescriptor = activity?.assets?.openFd(filename)
?: throw RuntimeException("Asset not found")

private var mediaPlayer: MediaPlayer = MediaPlayer()
private var metadata: Metadata? = AssetsMetadataExtractor().extract(assetFileDescriptor)

private val intensityChangeListener: ProgressChangeListener = object : ProgressChangeListener() {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
filter.run {
when (this) {
is GrainFilter -> setIntensity(transformGrain(progress))
is HueFilter -> setIntensity(transformHue(progress))
is AutoFixFilter -> setIntensity(transformAutofix(progress))
else -> activity?.showToast("Changing intensity not implemented for selected effect in this demo")
}
}
}
}

init {
setupMediaPlayer()
setupView()
}

private fun setupMediaPlayer() {
mediaPlayer.isLooping = true
mediaPlayer.setDataSource(
assetFileDescriptor.fileDescriptor,
assetFileDescriptor.startOffset,
assetFileDescriptor.length
)

// https://developer.android.com/reference/android/media/MediaPlayer.OnErrorListener
mediaPlayer.setOnErrorListener { _, what, extra ->
Log.d(TAG, "OnError! What: $what; Extra: $extra")
false
}

mediaPlayer.setOnCompletionListener {
Log.d(TAG, "OnComplete!")
}
}

private fun setupView() {
val metadata = this.metadata
val activity = this.activity
if (metadata != null && activity != null) {
activity.setupVideoSurfaceView(mediaPlayer, metadata.width, metadata.height)
activity.setupSeekBar(intensityChangeListener)
}
}

fun chooseShader() {
val videoWidth = metadata?.width?.toInt() ?: return
val videoHeight = metadata?.height?.toInt() ?: return

val dialog = ShaderChooserDialog.newInstance(videoWidth, videoHeight)
dialog.setListener(object : OnSelectShaderListener {
override fun onSelectShader(shader: Any) {
when (shader) {
is ShaderInterface -> {
filter = NoEffectFilter()
activity?.onSelectShader(shader)
}
is Filter -> {
filter = shader
activity?.onSelectFilter(shader)
}
else -> return
}
}
})

activity?.let {
dialog.show(it.supportFragmentManager, ShaderChooserDialog::class.java.simpleName)
}
}

@RequiresApi(Build.VERSION_CODES.M)
fun saveVideo() {

if (filter is NoEffectFilter) {
activity?.showToast("Saving will work only with Filters.")
}

val parent = activity?.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
?: throw RuntimeException("Activity is destroyed!")
val child = "out.mp4"
val outPath = File(parent, child).toString()
val assetConverterThread = AssetConverterThread(
AssetsConverter(assetFileDescriptor),
filter,
outPath,
object : ConvertResultListener {

override fun onSuccess() {
activity?.onFinishSavingVideo("Video successfully saved at $outPath")
}

override fun onFail() {
activity?.onFinishSavingVideo("Video wasn't saved. Check log for details.")
}
}
)

activity?.onStartSavingVideo()
assetConverterThread.start()
}

fun onPause() {
mediaPlayer.pause()
}

fun onDestroy() {
mediaPlayer.stop()
mediaPlayer.release()
assetFileDescriptor.close()
activity = null
}

@RequiresApi(Build.VERSION_CODES.M)
private class AssetConverterThread(private var assetsConverter: AssetsConverter,
private var filter: Filter,
private var outPath: String,
private var listener: ConvertResultListener) : Thread() {

override fun run() {
super.run()
assetsConverter.startConverter(filter, outPath, listener)
}
}
}
@@ -0,0 +1,9 @@
package com.videffects.sample.interfaces

interface OnSelectShaderListener {

/**
* Argument should implement ShaderInterface of Filter
*/
fun onSelectShader(shader: Any)
}
@@ -0,0 +1,17 @@
package com.videffects.sample.interfaces

import android.widget.SeekBar

/**
* Implement placeholder for unused methods of OnSeekBarChangeListener
*/
abstract class ProgressChangeListener : SeekBar.OnSeekBarChangeListener {

override fun onStartTrackingTouch(seekBar: SeekBar?) {

}

override fun onStopTrackingTouch(seekBar: SeekBar?) {

}
}
36 changes: 36 additions & 0 deletions app/src/main/java/com/videffects/sample/model/AssetsConverter.java
@@ -0,0 +1,36 @@
package com.videffects.sample.model;

import android.content.res.AssetFileDescriptor;

import com.sherazkhilji.videffects.model.Converter;
import com.sherazkhilji.videffects.model.Metadata;

import java.io.IOException;

public class AssetsConverter extends Converter {

public AssetsConverter(AssetFileDescriptor assetFileDescriptor) {
setMetadata(assetFileDescriptor);
try {
videoExtractor.setDataSource(
assetFileDescriptor.getFileDescriptor(),
assetFileDescriptor.getStartOffset(),
assetFileDescriptor.getLength());
audioExtractor.setDataSource(
assetFileDescriptor.getFileDescriptor(),
assetFileDescriptor.getStartOffset(),
assetFileDescriptor.getLength());
} catch (IOException e) {
e.printStackTrace();
}
}

private void setMetadata(AssetFileDescriptor assetFileDescriptor) {
Metadata metadata = new AssetsMetadataExtractor().extract(assetFileDescriptor);
if (metadata != null) {
width = (int) metadata.getWidth();
height = (int) metadata.getHeight();
bitrate = metadata.getBitrate();
}
}
}

0 comments on commit 1927979

Please sign in to comment.