open media state added
This commit is contained in:
@@ -200,21 +200,25 @@ class HelloSurface : SurfaceView, SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun render() {
|
fun render() {
|
||||||
persistentSurface?.let {
|
persistentSurface?.let {surface ->
|
||||||
val pCanvas = it.lockCanvas(null)
|
val pCanvas = surface.lockCanvas(null)
|
||||||
clearCanvas(pCanvas)
|
pCanvas?.let {
|
||||||
synchronized(it) {
|
clearCanvas(it)
|
||||||
drawHello(pCanvas, _frameNumber)
|
synchronized(surface) {
|
||||||
|
drawHello(it, _frameNumber)
|
||||||
|
}
|
||||||
|
surface.unlockCanvasAndPost(pCanvas)
|
||||||
}
|
}
|
||||||
it.unlockCanvasAndPost(pCanvas)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun preview() {
|
fun preview() {
|
||||||
val canvas = holder.lockCanvas()
|
val canvas = holder.lockCanvas()
|
||||||
clearCanvas(canvas)
|
canvas?.let {
|
||||||
drawHello(canvas, _frameNumber)
|
clearCanvas(it)
|
||||||
holder.unlockCanvasAndPost(canvas)
|
drawHello(it, _frameNumber)
|
||||||
|
holder.unlockCanvasAndPost(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||||
|
|||||||
@@ -12,10 +12,9 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Inspiry">
|
android:theme="@style/Theme.Inspiry">
|
||||||
<activity android:name=".MainActivity">
|
<activity android:name=".ui.MainActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|||||||
4
app/src/main/java/su/rst10h/inspiry/Constants.kt
Normal file
4
app/src/main/java/su/rst10h/inspiry/Constants.kt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package su.rst10h.inspiry
|
||||||
|
|
||||||
|
const val MEDIA_FOLDER = "test"
|
||||||
|
const val MEDIA_FILE_NAME = "TestVideo.mp4"
|
||||||
@@ -2,5 +2,7 @@ package su.rst10h.inspiry.data
|
|||||||
|
|
||||||
enum class ActivityState {
|
enum class ActivityState {
|
||||||
WAIT,
|
WAIT,
|
||||||
RENDER
|
RENDERING,
|
||||||
|
RENDERED,
|
||||||
|
OPEN_MEDIA
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ import android.view.Surface
|
|||||||
import su.rst10h.loopedworld.HelloSurface
|
import su.rst10h.loopedworld.HelloSurface
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@Deprecated("Это тоже работает, но кривовато - нет синхронизации фреймов. Лучше использовать VideoEncoder")
|
||||||
class CanvasRecorder(private val helloSurface: HelloSurface, appPath: File) {
|
class CanvasRecorder(private val helloSurface: HelloSurface, appPath: File) {
|
||||||
|
|
||||||
private var recorder: MediaRecorder
|
private var recorder: MediaRecorder
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import android.util.Log
|
|||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import su.rst10h.inspiry.MEDIA_FILE_NAME
|
||||||
|
import su.rst10h.inspiry.MEDIA_FOLDER
|
||||||
import su.rst10h.loopedworld.HelloSurface
|
import su.rst10h.loopedworld.HelloSurface
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -15,8 +17,10 @@ class VideoEncoder(private val baseFileDir: File, private val helloSurface: Hell
|
|||||||
|
|
||||||
private val bufferInfo = MediaCodec.BufferInfo()
|
private val bufferInfo = MediaCodec.BufferInfo()
|
||||||
private val mediaEncoder: MediaCodec
|
private val mediaEncoder: MediaCodec
|
||||||
private val inputSurface: Surface
|
private lateinit var inputSurface: Surface
|
||||||
|
|
||||||
var onStopRecord = {}
|
var onStopRecord = {}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1080, 1920)
|
val format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1080, 1920)
|
||||||
format.setInteger(
|
format.setInteger(
|
||||||
@@ -31,11 +35,14 @@ class VideoEncoder(private val baseFileDir: File, private val helloSurface: Hell
|
|||||||
inputSurface = mediaEncoder.createInputSurface()
|
inputSurface = mediaEncoder.createInputSurface()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var mediaMuxer: MediaMuxer
|
private lateinit var mediaMuxer: MediaMuxer
|
||||||
private var muxerTrackIndex = -1
|
private var muxerTrackIndex = -1
|
||||||
@Volatile var isRunning = false
|
@Volatile var isRunning = false
|
||||||
|
|
||||||
fun startRender() {
|
fun startRender() {
|
||||||
|
|
||||||
|
muxerTrackIndex = -1
|
||||||
val file = File(prepareFilePath(baseFileDir))
|
val file = File(prepareFilePath(baseFileDir))
|
||||||
if (!file.exists()) file.delete()
|
if (!file.exists()) file.delete()
|
||||||
mediaMuxer = MediaMuxer(file.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
|
mediaMuxer = MediaMuxer(file.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
|
||||||
@@ -60,8 +67,7 @@ class VideoEncoder(private val baseFileDir: File, private val helloSurface: Hell
|
|||||||
while (isRunning) {
|
while (isRunning) {
|
||||||
val encoderStatus = mediaEncoder.dequeueOutputBuffer(bufferInfo, 10000L)
|
val encoderStatus = mediaEncoder.dequeueOutputBuffer(bufferInfo, 10000L)
|
||||||
when(encoderStatus) {
|
when(encoderStatus) {
|
||||||
MediaCodec.INFO_TRY_AGAIN_LATER -> {
|
MediaCodec.INFO_TRY_AGAIN_LATER -> { }
|
||||||
}
|
|
||||||
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
|
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
|
||||||
Log.d("encoder", "start muxer $muxerTrackIndex")
|
Log.d("encoder", "start muxer $muxerTrackIndex")
|
||||||
val newFormat: MediaFormat = mediaEncoder.outputFormat
|
val newFormat: MediaFormat = mediaEncoder.outputFormat
|
||||||
@@ -83,17 +89,20 @@ class VideoEncoder(private val baseFileDir: File, private val helloSurface: Hell
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.d("encoder", "File closed")
|
Log.d("encoder","stop encoding")
|
||||||
mediaMuxer.stop()
|
mediaMuxer.stop()
|
||||||
|
mediaMuxer.release()
|
||||||
|
mediaEncoder.stop()
|
||||||
|
|
||||||
}
|
}
|
||||||
private fun prepareFilePath(appPath: File): String {
|
private fun prepareFilePath(appPath: File): String {
|
||||||
val fullPath = appPath.absolutePath+"/test/"
|
val fullPath = appPath.absolutePath+"/$MEDIA_FOLDER/"
|
||||||
if (!File(fullPath).exists()) {
|
if (!File(fullPath).exists()) {
|
||||||
if (!File(fullPath).mkdir()) {
|
if (!File(fullPath).mkdir()) {
|
||||||
Log.d("main", "directory test not created")
|
Log.d("main", "directory test not created")
|
||||||
throw Exception("The test directory does not exist and has not been created:\n$fullPath$\n")
|
throw Exception("The test directory does not exist and has not been created:\n$fullPath$\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fullPath+"TestVideoEncoder2.mp4"
|
return fullPath+MEDIA_FILE_NAME
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,38 +1,50 @@
|
|||||||
package su.rst10h.inspiry
|
package su.rst10h.inspiry.ui
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import su.rst10h.inspiry.MEDIA_FILE_NAME
|
||||||
|
import su.rst10h.inspiry.MEDIA_FOLDER
|
||||||
|
import su.rst10h.inspiry.R
|
||||||
import su.rst10h.inspiry.data.ActivityState
|
import su.rst10h.inspiry.data.ActivityState
|
||||||
|
|
||||||
import su.rst10h.loopedworld.HelloSurface
|
import su.rst10h.loopedworld.HelloSurface
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
val viewModel : MainViewModel by viewModels()
|
val viewModel : MainViewModel by viewModels()
|
||||||
|
|
||||||
val btn = findViewById<Button>(R.id.button)
|
val btn = findViewById<Button>(R.id.button)
|
||||||
val surfaceView = findViewById<HelloSurface>(R.id.testView)
|
val surfaceView = findViewById<HelloSurface>(R.id.testView)
|
||||||
|
|
||||||
viewModel.state.observe(this,{
|
viewModel.state.observe(this, { state ->
|
||||||
when(it) {
|
when (state) {
|
||||||
ActivityState.WAIT -> {
|
ActivityState.WAIT -> {
|
||||||
Log.d("main", "record stopped")
|
|
||||||
btn.text = "Render Template"
|
btn.text = "Render Template"
|
||||||
btn.isEnabled = true
|
btn.isEnabled = true
|
||||||
}
|
}
|
||||||
ActivityState.RENDER -> {
|
ActivityState.RENDERING -> {
|
||||||
Log.d("main", "record started")
|
btn.text = "RENDERING.."
|
||||||
btn.text = "Rendering.."
|
|
||||||
btn.isEnabled = false
|
btn.isEnabled = false
|
||||||
}
|
}
|
||||||
|
ActivityState.RENDERED -> {
|
||||||
|
btn.isEnabled = true
|
||||||
|
btn.text = "OPEN MEDIA"
|
||||||
|
}
|
||||||
|
ActivityState.OPEN_MEDIA -> {
|
||||||
|
openMedia()
|
||||||
|
viewModel.reset_state()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -41,7 +53,15 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
btn.setOnClickListener {
|
btn.setOnClickListener {
|
||||||
viewModel.renderTemplate()
|
viewModel.action()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private fun openMedia() {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.setDataAndType(Uri.parse(
|
||||||
|
getExternalFilesDir(null)?.absolutePath+
|
||||||
|
"/$MEDIA_FOLDER/$MEDIA_FILE_NAME"),
|
||||||
|
"video/mp4")
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
package su.rst10h.inspiry
|
package su.rst10h.inspiry.ui
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import su.rst10h.inspiry.data.*
|
import su.rst10h.inspiry.data.*
|
||||||
import su.rst10h.inspiry.data.ActivityState.*
|
import su.rst10h.inspiry.data.ActivityState.*
|
||||||
import su.rst10h.inspiry.repository.CanvasRecorder
|
|
||||||
import su.rst10h.inspiry.repository.VideoEncoder
|
import su.rst10h.inspiry.repository.VideoEncoder
|
||||||
import su.rst10h.loopedworld.HelloSurface
|
import su.rst10h.loopedworld.HelloSurface
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -23,18 +21,24 @@ class MainViewModel: ViewModel() {
|
|||||||
videoEncoder = VideoEncoder(appPath, surface)
|
videoEncoder = VideoEncoder(appPath, surface)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun renderTemplate() {
|
fun reset_state() {
|
||||||
|
state.postValue(WAIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun action() {
|
||||||
when (state.value) {
|
when (state.value) {
|
||||||
WAIT -> {
|
WAIT -> {
|
||||||
videoEncoder.onStopRecord = {
|
videoEncoder.onStopRecord = {
|
||||||
state.postValue(WAIT)
|
state.postValue(RENDERED)
|
||||||
}
|
}
|
||||||
videoEncoder.startRender()
|
videoEncoder.startRender()
|
||||||
state.postValue(RENDER)
|
state.postValue(RENDERING)
|
||||||
}
|
}
|
||||||
RENDER -> {
|
RENDERED -> {
|
||||||
state.postValue(WAIT)
|
state.postValue(OPEN_MEDIA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".MainActivity">
|
tools:context=".ui.MainActivity">
|
||||||
|
|
||||||
<su.rst10h.loopedworld.HelloSurface
|
<su.rst10h.loopedworld.HelloSurface
|
||||||
android:id="@+id/testView"
|
android:id="@+id/testView"
|
||||||
|
|||||||
Reference in New Issue
Block a user