日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android App开发实战项目之仿喜马拉雅的听说书App实现(超详细 附源码和演示视频)

發布時間:2023/12/29 Android 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android App开发实战项目之仿喜马拉雅的听说书App实现(超详细 附源码和演示视频) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

需要全部源碼請點贊關注收藏后評論區留下QQ~~~

一、需求分析

用戶不僅能在平臺上收聽音頻,還能成為內容創作者,總之長音頻分享平臺需要滿足兩種角色的使用:一種是作為內容創作者發布自己的音頻,另一種是作為用戶欣賞平臺上的已有音頻

二、功能分析

長音頻分享主要集成了如下App技術

1:網格控件

長音頻分享首頁的欄目列表,以網格形式排列

2:屬性動畫

在音頻錄制過程中 上方的風車圖標持續旋轉

3:彈幕動畫

在音頻收聽界面,可以劃過彈幕

4:音頻控制條

無論是用戶收聽音頻還是創作者試聽音頻 都需要音頻控制條協助播音

5:JNI接口

創作者錄制的原始音頻要求轉成MP3格式 需要借助第三方的LAME庫

6:網絡通信框架

上傳音頻信息與獲取音頻列表均與后端交互

7:圖片加載框架

音頻封面來自Web服務? 建議利用Glide框架加載網絡圖片

8:Socket通信

采取Socket通信與后端服務器交互

9:移動數據格式JSON

傳輸評論信息時? 需要把消息結構封裝為JSON結構

下面介紹一下源碼中各模塊之間的關系

StoryViewActivity? 長音頻分享的首頁列表

StoryListenActivity? 說書音頻的欣賞頁面

StoryTakeActivity? 音頻錄制頁面

StoryEditActivity? 音頻信息的編輯頁面

AudioLoadTask 首頁音頻列表的加載任務

與此同時 長音頻分享還需要與之配合的HTTP服務器和Socket服務器 此處不再贅述?

三、演示效果?

演示視頻已上傳至個人主頁 有需要可以前往觀看

首頁如下

?

點擊下方的話筒圖標 跳到音頻的初始界面 可以開始錄音

?

?

?同時可以給自己錄制的音頻添加圖案和音頻描述

?

?

?同時能在多臺手機上收聽和發出彈幕

?四、代碼

部分源碼如下 需要全部代碼以及依賴請點贊關注收藏后評論區留下QQ~~~

編輯類

package com.example.audio;import androidx.appcompat.app.AppCompatActivity;import android.app.ProgressDialog; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.text.TextUtils; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast;import com.example.audio.bean.CommitResponse; import com.example.audio.constant.UrlConstant; import com.example.audio.util.BitmapUtil; import com.example.audio.util.DateUtil; import com.example.audio.widget.AudioController; import com.google.gson.Gson;import java.io.File; import java.io.IOException;import okhttp3.Call; import okhttp3.Callback; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response;public class StoryEditActivity extends AppCompatActivity {private final static String TAG = "StoryEditActivity";private ImageView iv_cover; // 聲明一個圖像視圖對象private EditText et_artist; // 聲明一個編輯框對象private EditText et_title; // 聲明一個編輯框對象private EditText et_desc; // 聲明一個編輯框對象private AudioController ac_play; // 聲明一個音頻控制條對象private String mAudioPath; // 音頻文件路徑private int CHOOSE_CODE = 3; // 只在相冊挑選圖片的請求碼private Bitmap mCoverBitmap; // 聲明一個位圖對象private ProgressDialog mDialog; // 聲明一個對話框對象@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_story_edit);findViewById(R.id.iv_back).setOnClickListener(v -> finish());TextView tv_title = findViewById(R.id.tv_title);tv_title.setText("編輯說書音頻");iv_cover = findViewById(R.id.iv_cover);et_artist = findViewById(R.id.et_artist);et_title = findViewById(R.id.et_title);et_desc = findViewById(R.id.et_desc);ac_play = findViewById(R.id.ac_play);findViewById(R.id.iv_cover).setOnClickListener(v -> {// 創建一個內容獲取動作的意圖(準備跳到系統相冊)Intent albumIntent = new Intent(Intent.ACTION_GET_CONTENT);albumIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); // 是否允許多選albumIntent.setType("image/*"); // 類型為圖像startActivityForResult(albumIntent, CHOOSE_CODE); // 打開系統相冊});findViewById(R.id.btn_upload).setOnClickListener(v -> uploadAudio());mAudioPath = getIntent().getStringExtra("audio_path");ac_play.prepare(mAudioPath); // 準備播放指定路徑的音頻}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent intent) {super.onActivityResult(requestCode, resultCode, intent);if (resultCode == RESULT_OK && requestCode == CHOOSE_CODE) { // 從相冊返回if (intent.getData() != null) { // 從相冊選擇一張照片Uri uri = intent.getData(); // 獲得已選擇照片的路徑對象// 根據指定圖片的uri,獲得自動縮小后的位圖對象mCoverBitmap = BitmapUtil.getAutoZoomImage(this, uri);iv_cover.setImageBitmap(mCoverBitmap); // 設置圖像視圖的位圖對象}}}// 執行音頻上傳動作private void uploadAudio() {String artist = et_artist.getText().toString();String title = et_title.getText().toString();String desc = et_desc.getText().toString();if (TextUtils.isEmpty(artist)) {Toast.makeText(this, "請先輸入音頻作者名稱", Toast.LENGTH_SHORT).show();return;}if (TextUtils.isEmpty(title)) {Toast.makeText(this, "請先輸入音頻的標題", Toast.LENGTH_SHORT).show();return;}if (TextUtils.isEmpty(desc)) {Toast.makeText(this, "請先輸入音頻的描述", Toast.LENGTH_SHORT).show();return;}// 彈出進度對話框mDialog = ProgressDialog.show(this, "請稍候", "正在上傳音頻信息......");String coverPath = String.format("%s/%s.jpg",getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(),DateUtil.getNowDateTime());BitmapUtil.saveImage(coverPath, mCoverBitmap); // 把位圖保存為圖片文件// 下面把音頻信息(包含封面)提交給HTTP服務端MultipartBody.Builder builder = new MultipartBody.Builder();// 往建造器對象添加文本格式的分段數據builder.addFormDataPart("artist", artist); // 作者builder.addFormDataPart("title", title); // 標題builder.addFormDataPart("desc", desc); // 描述// 往建造器對象添加圖像格式的分段數據builder.addFormDataPart("cover", coverPath.substring(coverPath.lastIndexOf("/")),RequestBody.create(new File(coverPath), MediaType.parse("image/*")));// 往建造器對象添加音頻格式的分段數據builder.addFormDataPart("audio", mAudioPath.substring(mAudioPath.lastIndexOf("/")),RequestBody.create(new File(mAudioPath), MediaType.parse("audio/*")));RequestBody body = builder.build(); // 根據建造器生成請求結構OkHttpClient client = new OkHttpClient(); // 創建一個okhttp客戶端對象// 創建一個POST方式的請求結構Request request = new Request.Builder().post(body).url(UrlConstant.HTTP_PREFIX+"commitAudio").build();Call call = client.newCall(request); // 根據請求結構創建調用對象// 加入HTTP請求隊列。異步調用,并設置接口應答的回調方法call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) { // 請求失敗// 回到主線程操縱界面runOnUiThread(() -> {mDialog.dismiss(); // 關閉進度對話框Toast.makeText(StoryEditActivity.this,"上傳音頻信息出錯:"+e.getMessage(), Toast.LENGTH_SHORT).show();});}@Overridepublic void onResponse(Call call, final Response response) throws IOException { // 請求成功String resp = response.body().string();CommitResponse commitResponse = new Gson().fromJson(resp, CommitResponse.class);// 回到主線程操縱界面runOnUiThread(() -> {mDialog.dismiss(); // 關閉進度對話框if ("0".equals(commitResponse.getCode())) {finishUpload(); // 結束音頻上傳動作} else {Toast.makeText(StoryEditActivity.this, "上傳音頻信息失敗:"+commitResponse.getDesc(), Toast.LENGTH_SHORT).show();}});}});}// 結束音頻上傳動作private void finishUpload() {Toast.makeText(this, "成功上傳您的說書音頻", Toast.LENGTH_SHORT).show();// 下面重新打開音頻列表瀏覽界面Intent intent = new Intent(this, StoryViewActivity.class);// 設置啟動標志:跳轉到新頁面時,棧中的原有實例都被清空,同時開辟新任務的活動棧intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);}@Overrideprotected void onResume() {super.onResume();ac_play.resume(); // 恢復播放}@Overrideprotected void onPause() {super.onPause();ac_play.pause(); // 暫停播放}@Overrideprotected void onDestroy() {super.onDestroy();ac_play.release(); // 釋放播放資源}}

收聽類

package com.example.audio;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.view.WindowManager; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast;import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.request.RequestOptions; import com.example.audio.bean.AudioInfo; import com.example.audio.bean.JoinInfo; import com.example.audio.bean.MessageInfo; import com.example.audio.constant.UrlConstant; import com.example.audio.util.DateUtil; import com.example.audio.util.SocketUtil; import com.example.audio.util.Utils; import com.example.audio.util.ViewUtil; import com.example.audio.widget.AudioController; import com.example.audio.widget.BarrageView; import com.google.gson.Gson;import org.json.JSONObject;import io.socket.client.Socket;public class StoryListenActivity extends AppCompatActivity {private final static String TAG = "StoryListenActivity";private TextView tv_title; // 聲明一個文本視圖對象private TextView tv_artist; // 聲明一個文本視圖對象private ImageView iv_cover; // 聲明一個圖像視圖對象private TextView tv_desc; // 聲明一個文本視圖對象private BarrageView bv_comment; // 聲明一個彈幕視圖對象private AudioController ac_play; // 聲明一個音頻控制條對象private EditText et_input; // 聲明一個編輯框對象private String mSelfName, mGroupName; // 自己名稱,群組名稱private Socket mSocket; // 聲明一個套接字對象@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_story_listen);getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 保持屏幕常亮initView(); // 初始化視圖playStory(); // 播放故事音頻initSocket(); // 初始化套接字}// 初始化視圖private void initView() {findViewById(R.id.iv_back).setOnClickListener(v -> finish());tv_title = findViewById(R.id.tv_title);tv_artist = findViewById(R.id.tv_artist);iv_cover = findViewById(R.id.iv_cover);tv_desc = findViewById(R.id.tv_desc);bv_comment = findViewById(R.id.bv_comment);ac_play = findViewById(R.id.ac_play);et_input = findViewById(R.id.et_input);findViewById(R.id.btn_send).setOnClickListener(v -> sendMessage());}// 播放故事音頻private void playStory() {AudioInfo audio = (AudioInfo) getIntent().getSerializableExtra("audio_info");mSelfName = DateUtil.getFullDateTime();mGroupName = audio.getTitle();tv_artist.setText(String.format("《%s》%s", audio.getTitle(), audio.getArtist()));tv_title.setText(audio.getTitle());tv_desc.setText(audio.getDesc());// 使用Glide加載圓角矩形裁剪后的故事封面RoundedCorners roundedCorners = new RoundedCorners(Utils.dip2px(this, 30));RequestOptions options = RequestOptions.bitmapTransform(roundedCorners);Glide.with(this).load(UrlConstant.HTTP_PREFIX+audio.getCover()).apply(options).into(iv_cover);ac_play.prepareAsync(UrlConstant.HTTP_PREFIX+audio.getAudio()); // 準備播放指定鏈接的網絡音頻}// 初始化套接字private void initSocket() {mSocket = MainApplication.getInstance().getSocket();mSocket.connect(); // 建立Socket連接// 等待接收彈幕消息mSocket.on("receive_group_message", (args) -> {JSONObject json = (JSONObject) args[0];MessageInfo message = new Gson().fromJson(json.toString(), MessageInfo.class);// 往故事窗口添加彈幕評論runOnUiThread(() -> bv_comment.addComment(message.content));});// 下面向Socket服務器發送入群通知JoinInfo joinInfo = new JoinInfo(mSelfName, mGroupName);SocketUtil.emit(mSocket, "join_group", joinInfo);}// 發送評論消息private void sendMessage() {String content = et_input.getText().toString();if (TextUtils.isEmpty(content)) {Toast.makeText(this, "請輸入評論消息", Toast.LENGTH_SHORT).show();return;}et_input.setText("");ViewUtil.hideOneInputMethod(this, et_input); // 隱藏軟鍵盤bv_comment.addComment(content); // 給彈幕視圖添加評論// 下面向Socket服務器發送群消息MessageInfo message = new MessageInfo(mSelfName, mGroupName, content);SocketUtil.emit(mSocket, "send_group_message", message);}@Overrideprotected void onResume() {super.onResume();ac_play.resume(); // 恢復播放}@Overrideprotected void onPause() {super.onPause();ac_play.pause(); // 暫停播放}@Overrideprotected void onDestroy() {super.onDestroy();ac_play.release(); // 釋放播放資源if (mSocket.connected()) { // 已經連上Socket服務器// 下面向Socket服務器發送退群通知JoinInfo joinInfo = new JoinInfo(mSelfName, mGroupName);SocketUtil.emit(mSocket, "leave_group", joinInfo);mSocket.off("receive_group_message"); // 取消接收彈幕消息mSocket.disconnect(); // 斷開Socket連接}}}

創作不易 覺得有幫助請點贊關注收藏~~

總結

以上是生活随笔為你收集整理的Android App开发实战项目之仿喜马拉雅的听说书App实现(超详细 附源码和演示视频)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。