From e6e3df701d72c1890fc7d10832c032364b6572cf Mon Sep 17 00:00:00 2001 From: klaus Date: Fri, 2 Aug 2024 17:53:34 +0900 Subject: [PATCH] =?UTF-8?q?=ED=81=AC=EB=A6=AC=EC=97=90=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=20=EC=BB=A4=EB=AE=A4=EB=8B=88=ED=8B=B0=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EB=93=B1=EB=A1=9D=20-=20=EC=98=A4=EB=94=94?= =?UTF-8?q?=EC=98=A4=20=EB=85=B9=EC=9D=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../creator_community/CreatorCommunityApi.kt | 1 + .../CreatorCommunityRepository.kt | 2 + .../write/CreatorCommunityWriteActivity.kt | 29 +- .../write/CreatorCommunityWriteViewModel.kt | 30 ++ .../write/RecordingVoiceFragment.kt | 300 ++++++++++++++++++ .../activity_creator_community_write.xml | 57 ++++ .../res/layout/fragment_recording_voice.xml | 158 +++++++++ 7 files changed, 576 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/RecordingVoiceFragment.kt create mode 100644 app/src/main/res/layout/fragment_recording_voice.xml diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt index c409e2d..3948d98 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityApi.kt @@ -22,6 +22,7 @@ interface CreatorCommunityApi { @POST("/creator-community") @Multipart fun createCommunityPost( + @Part audioFile: MultipartBody.Part?, @Part postImage: MultipartBody.Part?, @Part("request") request: RequestBody, @Header("Authorization") authHeader: String diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt index c13c0a1..26e2adf 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/CreatorCommunityRepository.kt @@ -75,10 +75,12 @@ class CreatorCommunityRepository(private val api: CreatorCommunityApi) { ) fun createCommunityPost( + audioFile: MultipartBody.Part?, postImage: MultipartBody.Part?, request: RequestBody, token: String ) = api.createCommunityPost( + audioFile = audioFile, postImage = postImage, request = request, authHeader = token diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt index 434b49f..4f1d679 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/CreatorCommunityWriteActivity.kt @@ -24,10 +24,11 @@ import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.ActivityCreatorCommunityWriteBinding import kr.co.vividnext.sodalive.extensions.dpToPx import org.koin.android.ext.android.inject +import java.io.File class CreatorCommunityWriteActivity : BaseActivity( ActivityCreatorCommunityWriteBinding::inflate -) { +), RecordingVoiceFragment.OnAudioRecordedListener { private val viewModel: CreatorCommunityWriteViewModel by inject() @@ -51,7 +52,10 @@ class CreatorCommunityWriteActivity : BaseActivity + val buffer = ByteArray(512) + var bytesRead: Int + while (inputStream.read(buffer).also { bytesRead = it } != -1) { + sink.write(buffer, 0, bytesRead) + } + } + } + + override fun contentLength(): Long { + return audioFile!!.length() + } + } + ) + } else { + null + } + compositeDisposable.add( repository.createCommunityPost( + audioFile = multipartAudioFile, postImage = postImage, request = requestJson.toRequestBody("text/plain".toMediaType()), token = "Bearer ${SharedPreferenceManager.token}" diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/RecordingVoiceFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/RecordingVoiceFragment.kt new file mode 100644 index 0000000..8c86695 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/write/RecordingVoiceFragment.kt @@ -0,0 +1,300 @@ +package kr.co.vividnext.sodalive.explorer.profile.creator_community.write + +import android.annotation.SuppressLint +import android.app.Dialog +import android.content.Context +import android.media.MediaPlayer +import android.media.MediaRecorder +import android.os.Build +import android.os.Bundle +import android.os.CountDownTimer +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.Toast +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.databinding.FragmentRecordingVoiceBinding +import java.io.File +import java.io.IOException +import java.util.Locale + +class RecordingVoiceFragment : BottomSheetDialogFragment() { + + private var listener: OnAudioRecordedListener? = null + private var countDownTimer: CountDownTimer? = null + private var mediaRecorder: MediaRecorder? = null + private var mediaPlayer: MediaPlayer? = null + private var fileNameMedia = "" + + private var second = -1 + private var minute = 0 + private var hour = 0 + + private lateinit var binding: FragmentRecordingVoiceBinding + + interface OnAudioRecordedListener { + fun onAudioRecorded(file: File) + } + + override fun onAttach(context: Context) { + super.onAttach(context) + if (context is OnAudioRecordedListener) { + listener = context + } else { + throw RuntimeException("$context must implement OnAudioRecordedListener") + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + + dialog.setOnShowListener { + val d = it as BottomSheetDialog + val bottomSheet = d.findViewById( + com.google.android.material.R.id.design_bottom_sheet + ) + if (bottomSheet != null) { + BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED + } + } + + return dialog + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentRecordingVoiceBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.ivClose.setOnClickListener { dismiss() } + binding.ivRecordStart.setOnClickListener { + fileNameMedia = requireActivity().filesDir.path + + "/record_community_voice_${System.currentTimeMillis()}.m4a" + + val fileMedia = File(fileNameMedia) + if (!fileMedia.exists()) { + try { + fileMedia.createNewFile() + startRecording() + } catch (e: IOException) { + Toast.makeText(requireActivity(), R.string.retry, Toast.LENGTH_LONG).show() + e.printStackTrace() + } + } + } + + binding.ivRecordStop.setOnClickListener { stopRecording() } + binding.ivRecordPlay.setOnClickListener { startPlaying() } + binding.ivRecordPause.setOnClickListener { stopPlaying() } + + binding.tvDelete.setOnClickListener { + deleteAudioFile() + + binding.ivRecordStart.visibility = View.VISIBLE + binding.llRetryOrComplete.visibility = View.GONE + binding.rlRecordPlay.visibility = View.GONE + binding.soundVisualizer.visibility = View.GONE + } + + binding.tvRetryRecord.setOnClickListener { + deleteAudioFile() + + binding.ivRecordStart.visibility = View.VISIBLE + binding.llRetryOrComplete.visibility = View.GONE + binding.rlRecordPlay.visibility = View.GONE + binding.soundVisualizer.visibility = View.GONE + } + + binding.tvComplete.setOnClickListener { + listener?.onAudioRecorded(file = File(fileNameMedia)) + dismiss() + } + } + + override fun onDetach() { + super.onDetach() + listener = null + } + + override fun onDestroy() { + releaseMediaPlayer() + releaseMediaRecorder() + deleteAudioFile() + + super.onDestroy() + } + + private fun deleteAudioFile() { + if (fileNameMedia.isNotBlank()) { + val fileMedia = File(fileNameMedia) + if (fileMedia.exists()) { + fileMedia.delete() + } + fileNameMedia = "" + } + } + + private fun startRecording() { + releaseMediaRecorder() + + // safety check, don't start a new recording if one is already going + mediaRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MediaRecorder(requireContext()) + } else { + MediaRecorder() + }.apply { + setAudioSource(MediaRecorder.AudioSource.MIC) + setOutputFile(fileNameMedia) + setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) + setAudioEncoder(MediaRecorder.AudioEncoder.AAC) + setAudioEncodingBitRate(192000) + setAudioSamplingRate(48000) + } + + try { + mediaRecorder!!.prepare() + } catch (e: Exception) { + Toast.makeText(requireActivity(), R.string.retry, Toast.LENGTH_LONG).show() + return + } + + mediaRecorder!!.start() + binding.ivRecordStart.visibility = View.GONE + binding.ivRecordStop.visibility = View.VISIBLE + + startCountDownTimer() + } + + private fun releaseMediaRecorder() { + if (mediaRecorder != null) { + // stop recording and free up resources + mediaRecorder!!.stop() + mediaRecorder!!.reset() + mediaRecorder!!.release() + + mediaRecorder = null + } + } + + private fun stopRecording() { + releaseMediaRecorder() + stopCountDownTimer() + + binding.ivRecordStop.visibility = View.GONE + binding.rlRecordPlay.visibility = View.VISIBLE + binding.llRetryOrComplete.visibility = View.VISIBLE + } + + private fun startPlaying() { + if (mediaPlayer == null) { + mediaPlayer = MediaPlayer() + mediaPlayer!!.reset() + + mediaPlayer!!.setOnCompletionListener { + releaseMediaPlayer() + stopCountDownTimer() + + binding.tvDelete.visibility = View.VISIBLE + binding.ivRecordPlay.visibility = View.VISIBLE + binding.llRetryOrComplete.visibility = View.VISIBLE + binding.ivRecordPause.visibility = View.GONE + binding.soundVisualizer.visibility = View.GONE + } + + mediaPlayer!!.setOnPreparedListener { + binding.soundVisualizer.visibility = View.VISIBLE + binding.soundVisualizer.setAudioSessionId(mediaPlayer!!.audioSessionId) + it.start() + + startCountDownTimer() + } + + try { + mediaPlayer!!.setDataSource(fileNameMedia) + mediaPlayer!!.prepare() + } catch (e: Exception) { + Toast.makeText(requireActivity(), R.string.retry, Toast.LENGTH_LONG).show() + return + } + + binding.tvDelete.visibility = View.GONE + binding.ivRecordPlay.visibility = View.GONE + binding.llRetryOrComplete.visibility = View.GONE + binding.ivRecordPause.visibility = View.VISIBLE + } + } + + private fun stopPlaying() { + releaseMediaPlayer() + stopCountDownTimer() + + binding.tvDelete.visibility = View.VISIBLE + binding.ivRecordPlay.visibility = View.VISIBLE + binding.llRetryOrComplete.visibility = View.VISIBLE + binding.ivRecordPause.visibility = View.GONE + binding.soundVisualizer.visibility = View.GONE + } + + private fun releaseMediaPlayer() { + if (mediaPlayer != null) { + mediaPlayer!!.release() + mediaPlayer = null + } + } + + private fun startCountDownTimer() { + countDownTimer = object : CountDownTimer(Long.MAX_VALUE, 1000) { + override fun onTick(p0: Long) { + second += 1 + binding.tvTimer.text = recordingTime() + + if (second >= 180) { + stopRecording() + } + } + + override fun onFinish() { + } + } + + countDownTimer!!.start() + } + + private fun recordingTime(): String { + if (second == 60) { + minute += 1 + second = 0 + } + + if (minute == 60) { + hour += 1 + minute = 0 + } + + return String.format(Locale.getDefault(), "%02d:%02d:%02d", hour, minute, second) + } + + @SuppressLint("SetTextI18n") + private fun stopCountDownTimer() { + if (countDownTimer != null) { + countDownTimer!!.cancel() + countDownTimer = null + } + + binding.tvTimer.text = "00:00:00" + second = -1 + minute = 0 + hour = 0 + } +} diff --git a/app/src/main/res/layout/activity_creator_community_write.xml b/app/src/main/res/layout/activity_creator_community_write.xml index b50f5cf..4e474e4 100644 --- a/app/src/main/res/layout/activity_creator_community_write.xml +++ b/app/src/main/res/layout/activity_creator_community_write.xml @@ -94,6 +94,63 @@ android:textSize="13.3sp" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +