【从 0 开始开发一款直播 APP】12 直播封面上传

本文为菜鸟窝作者蒋志碧的连载。“从 0 开始开发一款直播 APP ”系列来聊聊时下最火的直播 APP,如何完整的实现一个类"腾讯直播"的商业化项目


直播封面上传功能运行效果

直播界面讲解

直播标题
    当前直播的标题,内容简介
直播封面
    封面图片会在如上图显示以及直播列表上显示
是否录制
    直播结束之后需要观看直播,就需要录制,直播结束之后录制的视频文件就叫点播,可以对视频进行永久的存储
摄像头直播
    根据主播需求打开前置摄像头还是后置摄像头
录屏直播
    只要用于屏幕录播,例如:游戏 在5.0以上
录制清晰度
    码流和分辨率(流畅,超清,高清)决定

界面布局不贴了,读者自己看着界面做,这里需要提示一下,界面上用到的自定义控件在文章中有讲,请戳链接。


【从 0 开始开发一款直播 APP】13 Android 6.0 运行时权限
【从 0 开始开发一款直播 APP】14 animation-list 逐帧动画自定义Switch控件


直播封面上传

直播封面上传功能有以下几个:
运行时权限验证
上传本地图片
上传相机图片

1、运行时权限验证

PublishPresenter # checkPublishPermission()

//1、权限通过 ActivityCompat 类的 checkSelfPermission() 方法判断是否有所需权限。
//2、权限请求是通过 ActivityCompat 类中的 requestPermissions() 方法,在OnRequestPermissionsResultCallback # onRequestPermissionsResult() 方法中回调。
//----------------------------split line---------------------------------------
@Override
public boolean checkPublishPermission(Activity activity) {
    if (Build.VERSION.SDK_INT >= 23) {
        List<String> permissions = new ArrayList<>();
        //写入外部存储设备权限(保存封面图片)
        if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(activity, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
            permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        //相机权限
        if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(activity, Manifest.permission.CAMERA)) {
            permissions.add(Manifest.permission.CAMERA);
        }
        //ActivityCompat.requestPermissions() 请求权限
        if (permissions.size() != 0) {
            ActivityCompat.requestPermissions(activity
                    , permissions.toArray(new String[0]),
                    Constants.WRITE_PERMISSION_REQ_CODE);
            return false;
        }
    }
    return true;
}

PublishActivity # onRequestPermissionsResult()
onRequestPermissionsResult() 处理请求权限

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    switch (requestCode) {
        //写入外部存储权限
        case Constants.WRITE_PERMISSION_REQ_CODE:
            for (int ret : grantResults) {
                if (ret != PackageManager.PERMISSION_GRANTED) {
                    return;
                }
            }
            mPermission = true;
            break;
    }
}

2、上传图片(相机、本地)

依然采用 MVP 架构。

【从 0 开始开发一款直播 APP】5.1 MVP 完全解析 -- 实现直播登录

View — IPublishView

public interface IPublishView extends BaseView{

    Activity getActivity();
    /**
     * 定位成功
     * @param location 位置
     */
    void doLocationSuccess(String location);
    /**
     * 定位失败
     */
    void doLocationFailed();
    /**
     * 图片上传成功
     * @param url 路径
     */
    void doUploadSuccess(String url);
    /**
     * 图片上传失败
     * @param url 路径
     */
    void doUploadFailed(String url);
    /**
     * 结束页面
     */
    void finishActivity();
}

Presenter — IPublishPresenter

public abstract class IPublishPresenter implements BasePresenter {
    protected BaseView mBaseView;
    public IPublishPresenter(BaseView baseView) {
        mBaseView = baseView;
    }
    /**
     * 检查推流权限
     * @param activity
     * @return
     */
    public abstract boolean checkPublishPermission(Activity activity);
    /**
     * 裁剪图片
     * @param imgUri 图片地址
     * @return
     */
    public abstract Uri cropImage(Uri imgUri);
    /**
     * 选择图片方式:相机、相册
     * @param mPermission 权限
     * @param type 类型
     * @return
     */
    public abstract Uri pickImage(boolean mPermission,int type);
    /**
     * 上传图片
     * @param path 图片路径
     */
    public abstract void doUploadPic(String path);
}

图片上传请求实体类


图片要上传到服务端,就需要请求网络,对图片上传封装一个请求实体。

public class UploadPicRequest  extends IRequest {
    //http://live.demo.cniao5.com/Api/Image/upload
    //请求参数:userId type file
    public UploadPicRequest(int requestId, String userId, int type, File file) throws FileNotFoundException {
        mRequestId = requestId;
        mParams.put("userId",userId);
        mParams.put("type",type);
        mParams.put("file",file);
    }
    @Override
    public String getUrl() {
        return getHost() + "Image/upload";
    }
    @Override
    public Type getParserType() {
        return new TypeToken<Response<UploadResp>>() {}.getType();
    }
}

PublishPresenter 具体实现

图片裁剪请查看:详细解释如何通过Android自带的方式来实现图片的裁剪——原理分析+解决方案

public class PublishPresenter extends IPublishPresenter {
    private IPublishView mIPublishView;
    private boolean mUploading = false;
    private String TAG = PublishPresenter.class.getSimpleName();
    public PublishPresenter(IPublishView iPublishView) {
        super(iPublishView);
        this.mIPublishView = iPublishView;
    }

    @Override
    public void start() {
    }

    @Override
    public void finish() {
        mIPublishView.finishActivity();
    }
  
    /**
     * 直接调用系统的图片裁剪功能
     * @param uri
     * @return
     */
    @Override
    public Uri cropImage(Uri uri) {
        Uri cropUri = createCoverUri("_crop");
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(uri, "image/*");//可以选择图片类型,如果是 * 表明所有类型的图片
        intent.putExtra("crop", "true");//设置在开启的Intent中设置显示的 view 可裁剪
        intent.putExtra("aspectX", 750);//裁剪图片的比例
        intent.putExtra("aspectY", 550);
        intent.putExtra("outputX", 750);//裁剪图片的宽
        intent.putExtra("outputY", 550);
        intent.putExtra("scale", true);//是否保持比例
        intent.putExtra("return-data", false);//是否返回bitmap
        intent.putExtra(MediaStore.EXTRA_OUTPUT, cropUri);//保存图片到指定uri
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());//输出格式
        mIPublishView.getActivity().startActivityForResult(intent, Constants.CROP_CHOOSE);//启动裁剪功能
        return cropUri;
    }
    //存储封面图片并保存uri地址
    private Uri createCoverUri(String preFileName) {
        String filename = ImUserInfoMgr.getInstance().getUserId() + preFileName + ".jpg";
        String path = Environment.getExternalStorageDirectory() + "/cniao_live";
        File outputImage = new File(path, filename);
        if (ContextCompat.checkSelfPermission(mIPublishView.getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(mIPublishView.getActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Constants.WRITE_PERMISSION_REQ_CODE);
            return null;
        }
        try {
            File pathFile = new File(path);
            if (!pathFile.exists())
                pathFile.mkdirs();

            if (outputImage.exists())
                outputImage.delete();
        } catch (Exception e) {
            e.printStackTrace();
            mIPublishView.showMsg("生成封面失败");
        }
        return Uri.fromFile(outputImage);
    }
    //选择封面图来源(相机、相册)
    @Override
    public Uri pickImage(boolean mPermission, int type) {
        Uri fileUri = null;
        if (!mPermission) {
            mIPublishView.showMsg("权限不足");
            return null;
        }
        switch (type) {
            //相机
            case Constants.PICK_IMAGE_CAMERA:
                fileUri = createCoverUri("");
                //启动相机
                Intent intent_photo = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                intent_photo.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
                mIPublishView.getActivity().startActivityForResult(intent_photo, Constants.PICK_IMAGE_CAMERA);
                break;
            //本地相册
            case Constants.PICK_IMAGE_LOCAL:
                fileUri = createCoverUri("_select");
                //打开相册
                Intent intent_album = new Intent("android.intent.action.GET_CONTENT");
                intent_album.setType("image/*");
                mIPublishView.getActivity().startActivityForResult(intent_album, Constants.PICK_IMAGE_LOCAL);
                break;
        }
        return fileUri;
    }
    //上传封面图
    @Override
    public void doUploadPic(String path) {
        mUploading = true;
        try {
            final UploadPicRequest request = new UploadPicRequest(1000,
                    ACache.get(mIPublishView.getContext()).getAsString("user_id"),
                    Constants.LIVE_COVER_TYPE,new File(path));
            AsyncHttp.instance().post(request, new AsyncHttp.IHttpListener() {
                @Override
                public void onStart(int requestId) {

                }
                @Override
                public void onSuccess(int requestId, Response response) {
                    if (response!=null) {
                        UploadResp resp = (UploadResp) response.getData();
                        Log.i(TAG, "onSuccess url:" + resp.getUrl());
                        mIPublishView.doUploadSuccess(resp.getUrl());
                    }else {
                        Log.i(TAG, "onSuccess url:");
                    }
                }
                @Override
                public void onFailure(int requestId, int httpStatus, Throwable error) {
                    Log.i(TAG, "onFailure :" + error);
                    mIPublishView.doLocationFailed();
                }
            });
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

封面图片上传实现

整个大致流程:首先检测权限,权限添加之后点击封面图片调用封面选择对话框,会出现三个按钮(相机、相册、取消)。
相机:打开相机功能进行拍照,然后确定之后会调用系统裁剪功能对图片进行裁剪,接着会将图片进行保存,裁剪之后再点击确定就会调用上传图片功能。
相册:打开相册选择,选择之后会调用系统裁剪功能对图片进行裁剪,接着会将图片进行保存,裁剪之后再点击确定就会调用上传图片功能。
取消:点击取消则退出当前对话框。

public class PublishActivity extends BaseActivity implements View.OnClickListener, IPublishView{
    //图片封面文字
    private TextView mTvPicTip;
    private Dialog mPicDialog;//选择封面对话框(照相机、相册、取消)
    private ImageView mImgCover;//封面图
    private Uri mFileUri, mCropUri;//原始图片文件uri,裁剪之后的图片文件uri
    private boolean mPermission = false;//权限监测

    private PublishPresenter mPublishPresenter;
    private String TAG = PublishActivity.class.getSimpleName();

    @Override
    protected void setActionBar() {
    }

    @Override
    protected void setListener() {
        mImgCover.setOnClickListener(this);
    }

    @Override
    protected void initData() {
        //初始化PublishPresenter
        mPublishPresenter = new PublishPresenter(this);
        //检测权限
        mPermission = mPublishPresenter.checkPublishPermission(this);
        String strCover = ACache.get(this).getAsString("head_pic");
        if (!TextUtils.isEmpty(strCover)) {
            Log.e(TAG, "head_pic:" + strCover);
            Glide.with(this).load(strCover).into(mImgCover);
            mTvPicTip.setVisibility(View.GONE);
        } else {
            mImgCover.setImageResource(R.drawable.publish_background);
        }
    }

    @Override
    protected void initView() {
        mTvPicTip = obtainView(R.id.tv_pic_tip);
        mImgCover = obtainView(R.id.cover);
        //初始化图片选择对话框
        initPhotoDialog();
    }

    /**
     * 封面图片选择对话框
     */
    private void initPhotoDialog() {
        //对话框初始化及样式设置
        mPicDialog = new Dialog(this, R.style.float_dialog);
        //对话框布局
        mPicDialog.setContentView(R.layout.dialog_pic_choose);
        WindowManager windowManager = getWindowManager();
        Display display = windowManager.getDefaultDisplay();
        Window window = mPicDialog.getWindow();
        WindowManager.LayoutParams lp = window.getAttributes();
        window.setGravity(Gravity.BOTTOM);
        lp.width = display.getWidth();
        mPicDialog.getWindow().setAttributes(lp);
        //按钮初始化并添加点击事件
        mPicDialog.findViewById(R.id.tv_chose_camera).setOnClickListener(this);
        mPicDialog.findViewById(R.id.tv_pic_lib).setOnClickListener(this);
        mPicDialog.findViewById(R.id.tv_dialog_cancel).setOnClickListener(this);
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_publish;
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            //封面图片选择
            case R.id.cover:
                mPicDialog.show();
                break;
            //相机,对拍摄图片地址进行存储,并对图片进行裁剪
            case R.id.tv_chose_camera:
                mFileUri = mPublishPresenter.pickImage(mPermission, Constants.PICK_IMAGE_CAMERA);
                mPicDialog.dismiss();
                break;
            //相册,从本地相册选择图片作为封面,并对图片进行裁剪,并将地址进行保存
            case R.id.tv_pic_lib:
                mFileUri = mPublishPresenter.pickImage(mPermission, Constants.PICK_IMAGE_LOCAL);
                mPicDialog.dismiss();
                break;
            //取消对话框按钮,表示不添加封面图片
            case R.id.tv_dialog_cancel:
                mPicDialog.dismiss();
                break;
        }
    }
    //相机和相册选择结果回调
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            switch (requestCode) {
                //选择相机图片
                case Constants.PICK_IMAGE_CAMERA:
                    mCropUri = mPublishPresenter.cropImage(mFileUri);
                    Log.d(TAG, "cropImage->path camera:" + mCropUri.getPath());
                    Log.d(TAG,"PICK_IMAGE_CAMERA 选择相机图片成功");
                    break;
                //选择本地相册图片
                case Constants.PICK_IMAGE_LOCAL:
                    String path = OtherUtils.getPath(this, data.getData());
                    if (null != path) {
                        Log.d(TAG, "cropImage->path local:" + path);
                        File file = new File(path);
                        mCropUri = mPublishPresenter.cropImage(Uri.fromFile(file));
                    }
                    Log.e(TAG,"PICK_IMAGE_LOCAL 选择本地图片成功");
                    break;
                //上传相机/相册图片
                case Constants.CROP_CHOOSE:
                    mTvPicTip.setVisibility(View.GONE);
                    Log.d(TAG, "cropImage->path crop:" + mCropUri.getPath());
                    mPublishPresenter.doUploadPic(mCropUri.getPath());
                    Log.d(TAG,"CROP_CHOOSE 上传图片成功");
                    break;
            }
        }
    }

    @Override
    public void doUploadSuccess(String url) {
        //加载封面图
        Glide.with(this).load(url).into(mImgCover);
    }

    @Override
    public void doUploadFailed(String url) {
        showMsg("直播封面上传失败");
    }
    //其他某些实现方法已被删,代码量太大,只贴出主要代码
    //......
}

运行效果

在控制台可以查看到打印的 Log 信息,先选择图片,然后裁剪之后进行上传。上传到服务端会返回一个 url 地址。打开 url 可以在浏览器中查看到图片。



在手机上找到 cniao_live 文件夹,并且有刚刚上传过的图片。

详情请转至 GitHub

特训营戳>>http://www.cniao5.com/hd/h5/android/fkcxy.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,376评论 25 707
  • ❤️晨起在洳霞老师的带领下做施与受冥想,本来想着继续观想大宝的咳嗽,但在老师的引导下,出现了另一个朋友的身影,所以...
    骞卉阅读 206评论 0 4
  • 那是最抑郁的一段时间,我就做事起劲那,带着耳机听着歌,突然一个电话打来了,然后就接了一下手机报了一下名字电话就挂了...
    方方_d10d阅读 267评论 0 1
  • 我们发现身边80%多的人工作不开心,那是什么让一些人从事着让人振奋,能改变世界的工作,让他们每天起床都充满干劲。又...
    纸影阅读 507评论 0 2