【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)

介绍

通过构建Short Video Creator这款短视频创建应用,您可以更好地了解华为生态系统的Serverless成员,如账号服务、云存储、云数据库、云函数等。该应用接入了强大的华为媒体服务及其它核心功能。得益于此,企业和用户可以在该应用平台上编辑和上传视频,将他们的才能让更多的人看到。

特点:

支持手机号登录

精彩视频,互动不停

免费使用,无限上传

具有视频编辑功能

头像上传

实时更新

在这个Codelab中,我们将使用如下华为移动服务来实现应用的相关功能:

应用功能

华为移动服务

手机号登录

账号服务

编辑、发布视频,上传头像 编辑完成后需借助本地存储和云存储确定如何在个人主页展示视频和头像。

视频编辑服务

上传视频和头像

云数据库

存储和获取用户信息,视频ID,视频标签,和视频评论等数据

云存储和云数据库

编辑个人主页照片

图像服务

个人主页广告展示

广告服务

您将建立什么

在这个Codelab中,您将建立一个名为Short Video Creator的短视频创建应用示例工程,使用视频编辑服务,账号服务,广告服务,图像服务,云存储和云数据库等华为服务的接口,并能学习到MVVM(Model–view–viewmodel)架构模式概念。

下图展示的是登录界面。当输入并确认手机号后,需要配合验证码完成登录。

【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第1张图片【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第2张图片

下图展示的是应用上播放的视频及其评论。

【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第3张图片【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第4张图片

下图展示的是视频编辑页面,用户可以在该页面上传视频和添加不同的音频片段。

【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第5张图片【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第6张图片【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第7张图片

下图展示如何添加音频片段。

【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第8张图片【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第9张图片【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第10张图片【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第11张图片

下图展示的是个人主页,用户可以在该页面看到已上传的视频,添加或更新个人头像。

【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第12张图片【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第13张图片【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第14张图片

为保护用户隐私,本文档中涉及用户个人信息的图片都已做了模糊处理。

您将学到什么

在这个Codelab中,您将学到:

  • 如何使用账号服务实现登录功能;
  • 如何使用云数据库储存用户和视频信息;
  • 如何使用云存储储存图像和视频;
  • 如何接入视频编辑服务和图像服务

您需要什么

说明:请提前准备上述硬件环境和相关设备。

硬件要求

  • 运行Windows 10操作系统的台式机或笔记本电脑。
  • 安装HMS Core (APK) 5.0.0.300或以上版本的华为手机一部。

软件要求

  • Android Studio 3.X
  • JDK 1.8或以上。
  • SDK Platform 23或以上。
  • Gradle 4.6或以上。

cke_169.png

说明:请提前准备上述软件环境。

能力接入准备

要集成HMS Core相关服务,需要完成以下准备:

  • 登录AppGallery Connect并创建应用。
  • 创建Android Studio工程。
  • 生成签名证书。
  • 生成签名证书指纹。
  • 配置签名证书指纹。
  • 添加应用包名并保存配置文件。
  • 在项目级build.gradle文件中,添加AppGallery Connect插件及Maven仓地址。
  • 在Android Studio中配置签名证书。

具体操作,请按照HMS Core集成准备中的详细说明来完成。

前往“项目设置”页面,选择“API”管理并开通下述服务:

  • 账号服务
  • 云数据库
  • 云存储
  • 视频编辑服务
  • 图像服务
  • 广告服务

【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第15张图片

说明:部分API默认是关闭的,您必须手动启用。

集成账号服务

全球约有十亿拥有华为账号的用户。接入华为账号的应用支持用户一键登录华为账号。此外,得益于庞大的华为用户群,您还可以拉新大量用户。

使用账号服务,您需要:

前往开发者联盟注册华为账号,登录AppGallery Connect创建应用,配置应用信息,然后开通账号服务。

在Android Studio中创建项目并接入账号服务SDK。

根据开发指南调用和调试账号服务接口。

开通账号服务。

1.添加如下依赖。

说明:根据开发文档集成最新版本的SDK,示例集成的是6.1.0.302版本。

implementation "com.huawei.hms:hwid:"

2.导入认证服务需要用到的相关类。

import com.huawei.hms.common.ApiException;

import com.huawei.hms.support.account.AccountAuthManager;

import com.huawei.hms.support.account.request.AccountAuthParams;

import com.huawei.hms.support.account.request.AccountAuthParamsHelper;

import com.huawei.hms.support.account.result.AuthAccount;

import com.huawei.hms.support.account.service.AccountAuthService;

3.编辑LoginActivity.java

btnSignup.setOnClickListener(v -> signup());
        Handler mHandler = new Handler(Looper.getMainLooper());
        if (AGConnectAuth.getInstance().getCurrentUser() == null) {
            mHandler.post(() -> {
                LoginHelper loginHelper = new LoginHelper(LoginActivity.this);
                loginHelper.addLoginCallBack(this);
                loginHelper.login();
            });
        }
private void signup(){
        try {
            AccountAuthParams mAuthParam = new AccountAuthParamsHelper(AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
                    .setIdToken()
                    .setAccessToken()
                    .createParams();
            AccountAuthService mAuthManager = AccountAuthManager.getService(LoginActivity.this, mAuthParam);
            startActivityForResult(mAuthManager.getSignInIntent(), Constants.REQUEST_SIGN_IN_LOGIN);
        }catch (Exception e){
            Log.e(Constants.APP_TAG,e.getMessage());
        }
    }
@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
if (requestCode == Constants.REQUEST_SIGN_IN_LOGIN) {
            Task authAccountTask = AccountAuthManager.parseAuthResultFromIntent(data);
            if (authAccountTask.isSuccessful()) {
                AuthAccount authAccount = authAccountTask.getResult();
                if(null !=authAccount.getEmail()) {
                    if (authAccount.getEmail().equalsIgnoreCase("0") || authAccount.getDisplayName().equalsIgnoreCase("0")) {
                        SharedPreferences.Editor editor = getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE).edit();
                        editor.putString("email", authAccount.getEmail());
                        editor.putString("mobile", authAccount.getDisplayName());
                        editor.putString("name", authAccount.getUid());
                        editor.putInt("login", 1);
                        editor.putInt("count", 1);
                        editor.apply();
                        getProfileDialog(authAccount.getEmail(), authAccount.getDisplayName(), authAccount.getUid());
 
                    }
                }
                else{
                    SharedPreferences.Editor editor = getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE).edit();
                    editor.putString("email", authAccount.getEmail());
                    editor.putString("mobile", authAccount.getDisplayName());
                    editor.putString("name", authAccount.getUid());
                    editor.putInt("login", 1);
                    editor.putInt("count", 1);
                    editor.apply();
                    getProfileDialog("", authAccount.getDisplayName(), authAccount.getUid());
 
                }
 
            } else {
                Log.i(Constants.APP_TAG, "signIn failed: " + ((ApiException) authAccountTask.getException()).getStatusCode());
            }
        }
    }

集成云数据库

本示例项目中需要将所有数据储存至云数据库中。

1.添加依赖。

a.添加如下云数据库依赖:

dependencies {

implementation "com.huawei.agconnect:agconnect-cloud-database:"

}

说明:请根据开发文档集成最新版本的云数据库,示例集成的是1.2.3.301版本。

b.接入SDK后,在应用级build.gradle文件头apply plugin: 'com.android.application'下添加如下声明。

apply plugin: 'com.huawei.agconnect'

2.创建一个包含指定字段的User对象。

Java示例

创建一个用来与云数据库交互用户信息的User类。

package com.huawei.tiktoksample.db.clouddb;
 
 
import com.huawei.agconnect.cloud.database.CloudDBZoneObject;
import com.huawei.agconnect.cloud.database.Text;
import com.huawei.agconnect.cloud.database.annotations.DefaultValue;
import com.huawei.agconnect.cloud.database.annotations.NotNull;
import com.huawei.agconnect.cloud.database.annotations.Indexes;
 import com.huawei.agconnect.cloud.database.annotations.PrimaryKeys;
 
@PrimaryKeys({"user_email"})
@Indexes({"user_id_email:user_email,user_id", "user_email:user_email"})
public final class User extends CloudDBZoneObject {
     private Integer user_id;

     private String user_email;

     private String user_name;

     private String user_profile_pic;

     @DefaultValue(booleanValue = true)
     private Boolean user_shadow_flag;

     private String user_phone;

     public User() {
         super(User.class);
         this.user_shadow_flag = true;
     }

     public void setUserId(Integer user_id) {
         this.user_id = user_id;
     }

     public Integer getUserId() {
         return user_id;
     }

     public void setUserEmail(String user_email) {
         this.user_email = user_email;
     }

     public String getUserEmail() {
         return user_email;
     }

     public void setUserName(String user_name) {
         this.user_name = user_name;
     }

     public String getUserName() {
         return user_name;
     }

     public void setUserProfilePic(String user_profile_pic) {
         this.user_profile_pic = user_profile_pic;
     }

     public String getUserProfilePic() {
         return user_profile_pic;
     }

     public void setUserShadowFlag(Boolean user_shadow_flag) {
         this.user_shadow_flag = user_shadow_flag;
     }

     public Boolean getUserShadowFlag() {
         return user_shadow_flag;
     }

     public void setUserPhone(String user_phone) {
         this.user_phone = user_phone;
     }

     public String getUserPhone() {
         return user_phone;
     }

 }

3.在CloudDBHelper类中导入启动、关闭和初始化云数据库的单例类。

Java示例

CloudDBHelper.java

import com.huawei.agconnect.cloud.database.AGConnectCloudDB;
import com.huawei.agconnect.cloud.database.CloudDBZone;
import com.huawei.agconnect.cloud.database.CloudDBZoneConfig;
import com.huawei.agconnect.cloud.database.exceptions.AGConnectCloudDBException;
import com.huawei.hmf.tasks.Task;
import com.huawei.tiktoksample.db.clouddb.ObjectTypeInfoHelper;
import com.huawei.tiktoksample.util.Constants;
import com.huawei.tiktoksample.util.OnDBZoneOpen;

4.添加如下代码段。

public class CloudDBHelper {

    private AGConnectCloudDB agConnectCloudDB;
    private CloudDBZone;
    private static final String TAG = CloudDBHelper.class.getSimpleName();

    private static CloudDBHelper;

    public static CloudDBHelper getInstance() {
        if (cloudDBHelper == null) {
            cloudDBHelper = new CloudDBHelper();
        }
        return cloudDBHelper;
    }

    public void init(Context context) {
        AGConnectCloudDB.initialize(context);
        try {
            agConnectCloudDB = AGConnectCloudDB.getInstance();
            agConnectCloudDB.createObjectType(ObjectTypeInfoHelper.getObjectTypeInfo());
        } catch (AGConnectCloudDBException e) {
            e.printStackTrace();
        }
    }

    public void openDb(OnDBZoneOpen onDBZoneOpen) {
        CloudDBZoneConfig mConfig = new CloudDBZoneConfig(Constants.DB_ZONE_NAME,
                CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
                CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
        mConfig.setPersistenceEnabled(true);
        Task openDBZoneTask = agConnectCloudDB.openCloudDBZone2(mConfig, true);
        openDBZoneTask.addOnSuccessListener(cloudDBZone -> {
            Log.i(TAG, "open cloudDBZone success");
            this.cloudDBZone = cloudDBZone;
            onDBZoneOpen.isDBZoneOpen(true, this.cloudDBZone);
        }).addOnFailureListener(e -> {
            Log.w(TAG, "open cloudDBZone failed for " + e.getMessage());
            onDBZoneOpen.isDBZoneOpen(false, null);
        });
    }

    public void closeDb(Context context) {
        try {
            agConnectCloudDB.closeCloudDBZone(cloudDBZone);
        } catch (AGConnectCloudDBException e) {
            e.printStackTrace();
        }
    }
}

5.初始化云数据库SDK,在Application类中调用如下方法。

public void init(Context context) {
    AGConnectCloudDB.initialize(context);
    try {
      agConnectCloudDB = AGConnectCloudDB.getInstance();
      agConnectCloudDB.createObjectType(ObjectTypeInfoHelper.getObjectTypeInfo());
    } catch (AGConnectCloudDBException e) {
        e.printStackTrace();
    }
}

6.建立与云数据库的连接。

    CloudDBZoneConfig mConfig = new CloudDBZoneConfig(Constants.DB_ZONE_NAME,
            CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
            CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
    mConfig.setPersistenceEnabled(true);
    Task openDBZoneTask = agConnectCloudDB.openCloudDBZone2(mConfig, true);
    openDBZoneTask.addOnSuccessListener(cloudDBZone -> {
        AppLog.logI(TAG, "cloudDBZOne Open");
        this.cloudDBZone = cloudDBZone;
        onDBZoneOpen.isDBZoneOpen(true, this.cloudDBZone);
    }).addOnFailureListener(e -> {
        AppLog.logW(TAG, "open cloudDBZone failed for " + e.getMessage());
        onDBZoneOpen.isDBZoneOpen(false, null);
    });
}

7.设置数据库连接关闭函数。

public void closeDb(Context context) {
    try {
        agConnectCloudDB.closeCloudDBZone(cloudDBZone);
    } catch (AGConnectCloudDBException e) {
        AppLog.logW(TAG,"close the DBZone "+e.getMessage());
    }
}

8.输入用户信息。

User user= new User();
user.setUserEmail(etEmail.getText().toString());
user.setUserName(etEmail.getText().toString());
user.setUserPhone(etMobile.getText().toString());
user.setUserId((int) System.currentTimeMillis());
user.setUserProfilePic(uri.toString());
user.setUserShadowFlag(true);
updateProfileData(user);
userProfile = new ViewModelProvider(LoginActivity.this).get(UserProfile.class);
 
public void updateProfileData(User user)
 {
     userProfile.saveUser(user, this);
 }

9.保存用户信息,检查是否成功保存。

public void saveUser(User user, Context context) {

    CloudDBHelper.getInstance().openDb((isConnected, cloudDBZone) -> {
        if (isConnected && cloudDBZone != null) {
            if (cloudDBZone == null) {
                return;
            } else {
                Task insertTask = cloudDBZone.executeUpsert(user);
                insertTask.addOnSuccessListener(integer -> {
                    userMutableLiveData.setValue(true);
                    CloudDBHelper.getInstance().closeDb(context);
                }).addOnFailureListener(e -> {
                    userMutableLiveData.setValue(false);
                    CloudDBHelper.getInstance().closeDb(context);
                });
            }
        }
    });
}

10.用户信息保存结果。

【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第16张图片

集成云存储

1.添加依赖。

dependencies{

implementation "com.huawei.agconnect:agconnect-storage:"

}

说明:请根据开发文档集成最新版本的云存储,示例集成的是1.3.1.100版本。

2.初始化云存储。

StorageReference storageReference = TikTokSampleApplication.getStorageManagement().getStorageReference("tiktoksample/user_video/"+file.getName());

3.设置云存储的ViewModel。

public class TikTokSampleViewModel extends ViewModel {

    private static final String TAG = "TikTokSampleViewModel";
    private MutableLiveData> userUploadedVideoList;
    private MutableLiveData> userVideoComentsList;
    private MutableLiveData uploadFileLiveData;
    private MutableLiveData insertVideoData;

    private MutableLiveData insertVideoComment;
    private MutableLiveData updateVideoLike;

    private OnApiError onApiError;

    public LiveData insertCommentForVideo() {
        if (insertVideoData == null) {
            insertVideoComment = new MutableLiveData<>();
        }
        return insertVideoComment;
    }

    public LiveData updateLikeForVideo() {
        if (updateVideoLike == null) {
            updateVideoLike = new MutableLiveData<>();
        }
        return updateVideoLike;
    }


    public LiveData uploadFileLiveData() {
        if (uploadFileLiveData == null) {
            uploadFileLiveData = new MutableLiveData<>();
        }
        return uploadFileLiveData;
    }

    public LiveData insertUserVideoData() {
        if (insertVideoData == null) {
            insertVideoData = new MutableLiveData<>();
        }
        return insertVideoData;
    }

    public void uploadFile(StorageReference reference, String fileName, File filePath, OnApiError onApiError) {
        this.onApiError = onApiError;
        UploadTask task = reference.putFile(filePath);
        task.addOnSuccessListener(uploadSuccessListener)
                .addOnFailureListener(uploadFailureListener);
    }

    OnSuccessListener uploadSuccessListener = new OnSuccessListener() {
        @Override
        public void onSuccess(UploadTask.UploadResult uploadResult) {
            uploadResult.getStorage().getDownloadUrl().addOnSuccessListener(downloadLink)
                    .addOnFailureListener(downloadUriFailureListener);
        }
    };

    OnSuccessListener downloadLink = new OnSuccessListener() {
        @Override
        public void onSuccess(Uri uri) {
            uploadFileLiveData.postValue(uri);
        }
    };

    OnFailureListener uploadFailureListener = new OnFailureListener() {
        @Override
        public void onFailure(Exception e) {
            if (onApiError != null) {
                onApiError.onError("Error in uploading file to server", e);
            }
        }
    };

    OnFailureListener downloadUriFailureListener = new OnFailureListener() {
        @Override
        public void onFailure(Exception e) {
            if (onApiError != null) {
                onApiError.onError("Failed in getting uri", e);
            }
        }
    };


    public void insertUserVideoData(Context context, VideoUpload videoupload, OnApiError onApiError) {
        CloudDBHelper.getInstance().openDb((isConnected, cloudDBZone) -> {
            if (isConnected && cloudDBZone != null) {
                if (cloudDBZone == null) {
                    return;
                } else {
                    Task insertTask = cloudDBZone.executeUpsert(videoupload);
                    insertTask.addOnSuccessListener(integer -> {
                        insertVideoData.setValue(true);
                        CloudDBHelper.getInstance().closeDb(context);
                    }).addOnFailureListener(e -> {
                        onApiError.onError(e.getLocalizedMessage(), e);
                        CloudDBHelper.getInstance().closeDb(context);
                    });
                }
            }
        });
    }

    public void insertUserData(Context context, User userData, OnApiError onApiError) {
        CloudDBHelper.getInstance().openDb((isConnected, cloudDBZone) -> {
            if (isConnected && cloudDBZone != null) {
                if (cloudDBZone == null) {
                    return;
                } else {
                    Task insertTask = cloudDBZone.executeUpsert(userData);
                    insertTask.addOnSuccessListener(integer -> {
                        insertVideoData.setValue(true);
                        CloudDBHelper.getInstance().closeDb(context);
                    }).addOnFailureListener(e -> {
                        onApiError.onError(e.getLocalizedMessage(), e);
                        CloudDBHelper.getInstance().closeDb(context);
                    });
                }
            }
        });
    }

    public void getUserList(Context context, OnApiError onApiError) {
        CloudDBHelper.getInstance().openDb((isConnected, cloudDBZone) -> {
            if (cloudDBZone == null) {
                AppLog.logE(TAG, "CloudDBZone is null, try re-open it");
                return;
            }
            Task> queryTask = cloudDBZone.executeQuery(
                    CloudDBZoneQuery.where(User.class),
                    CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
            queryTask.addOnSuccessListener((OnSuccessListener>) snapshot -> processQueryResult(snapshot)).addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(Exception e) {
                    AppLog.logE(getClass().getName(), "Error in getting data from user table");
                }
            });

        });
    }

    private void processQueryResult(CloudDBZoneSnapshot snapshot) {
        CloudDBZoneObjectList bookInfoCursor = snapshot.getSnapshotObjects();
        List bookInfoList = new ArrayList<>();
        try {
            while (bookInfoCursor.hasNext()) {
                User userInfo = bookInfoCursor.next();
                bookInfoList.add(userInfo);
            }
        } catch (AGConnectCloudDBException e) {
            AppLog.logE(TAG, "processQueryResult: " + e.getMessage());
        }

        AppLog.logE(getClass().getName(), "We got user data " + bookInfoList.toString());
        snapshot.release();
    }


    public void updateLikeCount(VideoUpload videoUpload) {
        CloudDBHelper.getInstance().openDb((isConnected, cloudDBZone) -> {
            if (cloudDBZone == null) {
                AppLog.logE(TAG, "CloudDBZone is null, try re-open it");
                return;
            }
            Task queryTask = cloudDBZone.executeUpsert(videoUpload);
            queryTask
                    .addOnSuccessListener(integer ->
                            updateVideoLike.postValue(integer)
                    )
                    .addOnFailureListener(e -> {
                        onApiError.onError(e.getLocalizedMessage(), e);
            });

        });
    }

    public void enterCommentForVideo(VideoComments videoComments, OnApiError onApiError) {

        CloudDBHelper.getInstance().openDb((isConnected, cloudDBZone) -> {

            if (cloudDBZone == null) {
                AppLog.logE(TAG, TikTokSampleApplication.getInstance().getString(R.string.err_cloud_db_zone));
                onApiError.onError(TikTokSampleApplication.getInstance().getString(R.string.err_cloud_db_zone), new Throwable(TikTokSampleApplication.getInstance().getString(R.string.err_cloud_db_zone)));
                return;
            }

            Task enterComment = cloudDBZone.executeUpsert(videoComments);
            enterComment.addOnSuccessListener(integer -> {
                insertVideoComment.postValue(integer);
            }).addOnFailureListener(e -> {
                onApiError.onError(e.getMessage(), e);
            });

        });

    }
    public LiveData> getUsersListOfVideo(){
        if(userUploadedVideoList==null){
            userUploadedVideoList = new MutableLiveData<>();
        }
        return userUploadedVideoList;
    }
    public LiveData> getUsersListOfComments(){
        if(userVideoComentsList==null){
            userVideoComentsList = new MutableLiveData<>();
        }
        return userVideoComentsList;
    }
    public void getAllUserUploadedVideoList(String userEmail, OnApiError onApiError){

        CloudDBHelper.getInstance().openDb((isConnected, cloudDBZone) -> {
            if (cloudDBZone == null) {
                AppLog.logE(TAG, "CloudDBZone is null, try re-open it");
                return;
            }
            Task> queryTask = cloudDBZone.executeQuery(
                    CloudDBZoneQuery.where(VideoUpload.class).equalTo("user_email", userEmail),
                    CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
            queryTask.addOnSuccessListener(this::processVideoUploadResult).addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(Exception e) {
                    AppLog.logE(getClass().getName(), "Error in getting data from user table");
                }
            });

        });

    }

    private void processVideoUploadResult(CloudDBZoneSnapshot snapshot) {
        CloudDBZoneObjectList bookInfoCursor = snapshot.getSnapshotObjects();
        List videoUploads = new ArrayList<>();
        try {
            while (bookInfoCursor.hasNext()) {
                VideoUpload userInfo = bookInfoCursor.next();
                videoUploads.add(userInfo);
            }
            userUploadedVideoList.postValue(videoUploads);
        } catch (AGConnectCloudDBException e) {
            AppLog.logE(TAG, "processQueryResult: " + e.getMessage());
        }

        AppLog.logE(getClass().getName(), "We got user data " + videoUploads.toString());
        snapshot.release();
    }
    public void getComments(Long videoId)
    {
        CloudDBHelper.getInstance().openDb((isConnected, cloudDBZone) -> {
            if (cloudDBZone == null) {
                AppLog.logE("TAG", "CloudDBZone is null, try re-open it");
                return;
            }
            Task> queryTask = cloudDBZone.executeQuery(
                    CloudDBZoneQuery.where(VideoComments.class).equalTo("video_id",videoId),
                    CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
            queryTask.addOnSuccessListener(snapshot -> processQueryCommentsResult(snapshot))
                    .addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(Exception e) {
                            AppLog.logE(getClass().getName(), "Error in getting data from user table");
                        }
                    });

        });
    }
    private void processQueryCommentsResult(CloudDBZoneSnapshot snapshot) {
        CloudDBZoneObjectList videoInfoCursor = snapshot.getSnapshotObjects();
        List commentList = new ArrayList<>();
        try {
            while (videoInfoCursor.hasNext()) {
                VideoComments videoComments = videoInfoCursor.next();
                commentList.add(videoComments);
            }

            userVideoComentsList.postValue(commentList);
        } catch (AGConnectCloudDBException e) {
            AppLog.logE("TAG", "processQueryResult: " + e.getMessage());
        }
        snapshot.release();
        Log.e("new test", "new test");
    }
}

4.上传文件。

private void uploadVideoToStorage(String filePath){
    File file = new File(filePath);
    StorageReference storageReference = TikTokSampleApplication.getStorageManagement().getStorageReference("tiktoksample/user_video/"+file.getName());
    tiktokStorageViewModel.uploadFile(storageReference,
            file.getName(), file, onApiError);
}

5.查存储结果。

【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第17张图片【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第18张图片

集成视频编辑服务

集成视频编辑服务能够简易您的视频编辑功能的开发。该服务提供下述SDK:

UI SDK:提供产品级UI界面,集成简单。

原子能力SDK:提供数百个原子能力接口,包含多个AI算法能力接口,可根据业务场景灵活选择。

这两种方式均提供导入、编辑、渲染、导出、媒体资源管理等一站式视频编辑能力,为您提供性能优异、简单易用、高兼容性的接口,帮助您轻松地构建应用。您可根据使用场景选择不同的集成方式获取视频编辑能力。

1.前往AppGallery Connect开通视频编辑服务。

【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第19张图片

2.添加如下依赖。

dependencies {

implementation 'com.huawei.hms:video-editor-ui:'

}

说明:根据开发文档集成最新版本的视频编辑服务,示例集成的是1.1.0.301版本。

3.在Manifest文件中添加权限。














4.应用初始化时调用setApiKey方法设置api_key(仅需设置一次)。

    UUID uuid = UUID.randomUUID();
    MediaApplication.getInstance().setLicenseId(uuid.toString());
    AGConnectServicesConfig config = AGConnectServicesConfig.fromContext(this);
    MediaApplication.getInstance().setApiKey(config.getString("client/api_key"));
    MediaApplication.getInstance().setOnMediaExportCallBack(CALL_BACK);
}

5.调用START_MODE_IMPORT_FROM_DRAFT,通过草稿模式进入Video Editor Kit UI界面。

private void initEvent() {
    draftAdapter.setSelectedListener(new DraftAdapter.OnStyleSelectedListener() {
        @Override
        public void onStyleSelected(int position) {
            String draftId = draftInfos.get(position).getDraftId();
            Log.d("Video_path", draftInfos.get(position).getDraftId()+" "+draftInfos.get(position).getDraftCoverPath());
            MediaApplication.getInstance()
                    .launchEditorActivity(VideoEditActivity.this,
                            new VideoEditorLaunchOption.Builder()
                                    .setStartMode(MediaApplication.START_MODE_IMPORT_FROM_DRAFT)
                                    .setDraftId(draftId)
                                    .build());
        }
    });

    draftAdapter.setLongSelectedListener(new DraftAdapter.OnStyleLongSelectedListener() {
        @Override
        public void onStyleLongSelected(View v, int position) {
            VideoEditActivity.this.position = position;
            PopupMenu popup = new PopupMenu(VideoEditActivity.this, v);
            MenuInflater inflater = popup.getMenuInflater();
            inflater.inflate(R.menu.menu, popup.getMenu());
            popup.setOnMenuItemClickListener(VideoEditActivity.this);
            popup.show();
        }
    });
    mSetting.setOnClickListener(v -> this.startActivity(new Intent(VideoEditActivity.this, SettingActivity.class)));
}

6.调用setOnMediaExportCallBack设置导出的回调。

private final MediaExportCallBack CALL_BACK = new MediaExportCallBack() {
    @Override
    public void onMediaExportSuccess(MediaInfo mediaInfo) {
        AppLog.logE(getClass().getSimpleName(),"length of media info "+mediaInfo.getMediaPath().length());
        mediaPath = mediaInfo.getMediaPath();
        Log.i(TAG, "The current video export path is" + mediaPath);
        uploadVideoToStorage(mediaPath);
    }
    @Override
    public void onMediaExportFailed(int errorCode) {
        Log.d(TAG, "errorCode" + errorCode);
    }
};

集成图像服务

图像服务通过华为场景智能设计服务SDK和华为场景动效服务SDK为您提供图像智能编辑与设计、场景动效制作能力。通过集成该服务提供的SDK,您可以快速集成24种滤镜、9大智能动效、图像主题标签、贴纸花字、图片裁剪、动画等能力。

1.添加如下依赖。

dependencies {

implementation "com.huawei.hms:image-vision:"

}

说明:根据开发文档集成最新版本的图像服务,示例集成的是1.0.3.301版本。

2.在Manifest文件中声明如下权限。







3.添加如下代码段至Java文件中。

public class EditImageActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btnDone;
    private CropLayoutView cropLayoutView;
    private RadioButton rbCircular;
    public static final String MY_PREFS_NAME = "MyPrefsFile";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_crop);
        cropLayoutView = findViewById(R.id.cropImageView);
        Button cropImage = findViewById(R.id.btn_crop_image);
        Button rotate = findViewById(R.id.btn_rotate);
        Button flipH = findViewById(R.id.btn_flip_horizontally);
        Button flipV = findViewById(R.id.btn_flip_vertically);
        btnDone = (Button) findViewById(R.id.btnDone);
        btnDone.setEnabled(false);
        cropLayoutView.setAutoZoomEnabled(true);
        cropLayoutView.setCropShape(CropLayoutView.CropShape.RECTANGLE);
        cropImage.setOnClickListener(this);
        rotate.setOnClickListener(this);
        flipH.setOnClickListener(this);
        flipV.setOnClickListener(this);
        rbCircular = findViewById(R.id.rb_circular);
        RadioGroup rgCrop = findViewById(R.id.rb_crop);
        Intent intent = getIntent();
        Bitmap bitmap = (Bitmap) intent.getParcelableExtra("BitmapImage");
        rgCrop.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup radioGroup, int i) {
                RadioButton radioButton = radioGroup.findViewById(i);
                if (radioButton.equals(rbCircular)) {
                    cropLayoutView.setCropShape(CropLayoutView.CropShape.OVAL);
                } else {
                    cropLayoutView.setCropShape(CropLayoutView.CropShape.RECTANGLE);
                }
            }
        });
        btnDone.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bitmap croppedImage = cropLayoutView.getCroppedImage();


                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                croppedImage.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
                byte[] byteArray = byteArrayOutputStream .toByteArray();
                String encoded = Base64.encodeToString(byteArray, Base64.DEFAULT);
                SharedPreferences.Editor editor = getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE).edit();
                editor.putString("image", encoded);
                editor.apply();
                if(LoginActivity.profileImagePage==1){
                    Intent myIntent = new Intent(EditImageActivity.this, LoginActivity.class);
                    myIntent.putExtra("NewBitmapImage", croppedImage);
                    startActivity(myIntent);
                }
                else {
                    Intent myIntent = new Intent(EditImageActivity.this, MainActivity.class);
                    myIntent.putExtra("NewBitmapImage", croppedImage);
                    startActivity(myIntent);
                }
            }
        });
        Spinner spinner = (Spinner) findViewById(R.id.spinner1);
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView parent, View view, int pos, long id) {
                String[] ratios = getResources().getStringArray(R.array.ratios);
                try {
                    int ratioX = Integer.parseInt(ratios[pos].split(":")[0]);
                    int ratioY = Integer.parseInt(ratios[pos].split(":")[1]);
                    cropLayoutView.setAspectRatio(ratioX, ratioY);
                } catch (Exception e) {
                    cropLayoutView.setFixedAspectRatio(false);
                }
            }

            @Override
            public void onNothingSelected(AdapterView parent) {
            }
        });
        RadioButton rbRectangle = findViewById(R.id.rb_rectangle);
        cropLayoutView.setImageBitmap(bitmap);


    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_crop_image:
                btnDone.setEnabled(true);
                Bitmap croppedImage = cropLayoutView.getCroppedImage();
                cropLayoutView.setImageBitmap(croppedImage);
                break;
            case R.id.btn_rotate:
                btnDone.setEnabled(true);
                cropLayoutView.rotateClockwise();
                break;
            case R.id.btn_flip_horizontally:
                btnDone.setEnabled(true);
                cropLayoutView.flipImageHorizontally();
                break;
            case R.id.btn_flip_vertically:
                btnDone.setEnabled(true);
                cropLayoutView.flipImageVertically();
                break;
        }
    }
}

效果图

【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第20张图片

集成广告服务

1.添加如下依赖。

dependencies {

implementation "com.huawei.hms:ads-lite:"

}

说明:根据开发文档集成最新版本的广告服务,示例集成的是13.4.52.302版本。

2.初始化广告服务SDK。

@Override
public void onCreate() {
    super.onCreate();
    mInstance = this;
    HwAds.init(this);

}

3.编辑XML布局文件。

private void callBannerAdd() {
    AdParam adParam = new AdParam.Builder().build();
    BannerView topBannerView = new BannerView(this);
    topBannerView.setAdId(getString(R.string.banner_ad_id));
    topBannerView.setBannerAdSize(BannerAdSize.BANNER_SIZE_SMART);
    topBannerView.loadAd(adParam);
    RelativeLayout rootView = findViewById(R.id.root_view);
    rootView.addView(topBannerView);
}

4.添加下述代码段至Java文件中。

private void callBannerAdd() {
    AdParam adParam = new AdParam.Builder().build();
    BannerView topBannerView = new BannerView(this);
    topBannerView.setAdId(getString(R.string.banner_ad_id));
    topBannerView.setBannerAdSize(BannerAdSize.BANNER_SIZE_SMART);
    topBannerView.loadAd(adParam);
    RelativeLayout rootView = findViewById(R.id.root_view);
    rootView.addView(topBannerView);
}

效果图

【多服务场景化解决方案】短视频剪辑(ShortVideoCreator)_第21张图片

恭喜您

祝贺您,您已成功构建出一款短视频创建应用。

参考

有关更多信息,请参阅以下官方文档。

  • 视频编辑服务
  • 账号服务
  • 云数据库
  • 图像服务
  • 广告服务
  • 云存储

注意:本Codelab可用做单项目中集成多个HMS服务的参考。您需要验证并确保相关源码合法和安全。

本Codelab中的示例代码下载地址:源码下载

欲了解更多更全技术文章,欢迎访问https://developer.huawei.com/consumer/cn/forum/?ha_source=zzh

你可能感兴趣的:(HMS,Core,AGC,java)