探索 5G 加持下的音视频技术创新 融云 X-Meetup 北京站即将开启! 置顶

技术交流梅川酷子 发表了文章 • 0 个评论 • 39 次浏览 • 2020-11-16 16:03 • 来自相关话题

随着 5G 的商用,我们将进入“万物互联”的时代,网络能力迎来飞跃式发展,互联网通信基础设施质量也逐步提升。在 5G 的加持下,音视频技术的应用边界将不断外延,衍生出各种创新应用场景。如何为以通信技术赋能更多音视频场景?北京  11 月 2... ...查看全部

随着 5G 的商用,

我们将进入“万物互联”的时代,

网络能力迎来飞跃式发展,

互联网通信基础设施质量也逐步提升。

在 5G 的加持下,

音视频技术的应用边界将不断外延,

衍生出各种创新应用场景。

如何为以通信技术赋能更多音视频场景?


北京  11 月 21 日

融云 X-Meetup

邀请五位顶级技术专家

给你答案!

扫码报名.png

meetup海报.jpg


【征稿活动】Geek Online 社区第一期投稿激励计划已启动! 置顶

活动梅川酷子 发表了文章 • 3 个评论 • 262 次浏览 • 2020-08-27 19:40 • 来自相关话题

为了回馈社区用户长期以来的支持,营造良好的社区技术氛围,鼓励更多开发者交流技术心得、分享技术实操方法及经验,创作更多优秀内容。特面向所有社区注册用户,举办第一期 Geek Online 社区投稿激励计划活动。活动时间征稿时间:8 月 27 日 – 10&nbs... ...查看全部

为了回馈社区用户长期以来的支持,营造良好的社区技术氛围,鼓励更多开发者交流技术心得、分享技术实操方法及经验,创作更多优秀内容。特面向所有社区注册用户,举办第一期 Geek Online 社区投稿激励计划活动。

活动时间

征稿时间:8 月 27 日 – 10 月 31 

评审时间:9、10 月每月最后一周进行

面向对象

Geek Online 社区所有注册用户

内容要求

对技术的介绍、产品的分析等技术类内容均可,也可以是个人实际操作的方法或经验总结,可供参考方向:

1、融云产品相关,选题包括:集成 IM 或 RTC 的使用体验、操作攻略、案例分享等。

2、结合市面上热度较高的事件/现象,从技术视角挖掘开发逻辑、产品解读等。

参赛规则

1、投稿需符合上述内容要求,图文并茂,排版美观,无错别字,代码规范。

2、在 Geek Online 社区发布文章参与,标题格式为【GeekOnline 投稿 | XXXXXXXXXX

3、稿件字数在 500 字以上

5、投稿数量不限,但所有稿件内容必须为 2020 年 8 月 27 日后作者本人新发内容

6、投稿人社区账号头像、昵称、个人介绍需资料完善

7、稿件投递后 个工作日内完成初审,并通过社区消息、邮箱通知。

评分细则

1、每月底将对当月参赛稿件进行评比,满分 10 。其中,

影响力 3 分,由社区内热度(阅读数)、互动量(点赞、评论数)加权计算;

专业性 7 分,由 Geek Online 技术专家及编辑组成的评审团,从文章实用性、创新性及代码规范度等方面综合评定给分。

2、我们鼓励投稿内容与融云产品相结合,对于此类稿件将给予社区置顶等曝光机会,增加文章影响力。

奖励

1、所有稿件通过初评,即可获得 Geek Online 加油包 1 份(内容发布 7 个工作日内发出)

2月度评选后,根据得分,给予优秀稿件 200-1000 元京东购物卡奖励,并在社区公示

3、优秀稿件,经编辑审核后,将安排在不限于公众号、社群及网媒中推广。

4、获奖作者,有机会加入社区特邀专栏作者计划,获得更高现金激励,并受邀参与融云各类开发者活动(线下/线上)

声明

1、在法律允许范围内,活动最终解释权归 Geek Online 社区所有。

2、参加活动的文章作者拥有著作权,Geek Online 社区、融云全媒体平台拥有使用权。

3、对于作者发布非原创内容或有争议内容所引起的一切后果,均由作者承担。欢迎社区用户举报,一经查实,作废处理。

android vivox27聊天界面点击拍照直接退出

BUG/建议反馈admin 回复了问题 • 2 人关注 • 1 个回复 • 16 次浏览 • 5 天前 • 来自相关话题

融云即时通讯SDK集成 — 定制UI(二) ——添加自定义表情库

技术交流王叫兽 发表了文章 • 0 个评论 • 28 次浏览 • 2020-11-18 17:43 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4
这个任务当然还是落在我的头上. 我是使用的他们家的带UI的sdk,(他们家有带UI和不带UI的两种sdk, 不带UI的sdk就是只有即时通讯能力, 所有的UI都需要开发者自定实现, 带UI的sdk封装了一些基本的界面,例如会话列表, 和别人聊天的会话界面.)当然这些已经集成了UI的sdk并不能完全满足一个产品的需求, 所以这篇文章跟大家讲下如何添加一套自定义的表情包.

效果如下哈:

QQ20201112-0.jpg

虽然这里有点难看了哈哈, 不过是为了给大家展示方法嘛, 就不管那么多了. 可以看到底下除了默认的emoji的表情包, 还多了一个tab, 我是从QQ表情搞了一套, 直接就加在这里了. 一个可爱的小猪猪哈哈哈.

添加步骤

需要改动的有这么几个类:

AndroidEmoji: 控制emoji图标资源, 编码, 以及相应展示的类
RongExtension: 会话界面除去聊天气泡与title bar的整个下方输入区域
IEmoticonTab: 表情tab
DefaultExtensionModule: 表情tab的上层控件.

对于AndroidEmoji这个类, 可以直接照抄, 把资源文件替换成自己准备好的图标, 以及编码/描述

public class ConversationListAdapter extends BaseAdapter<UIConversation> {
    private final static String TAG = "ConversationListAdapter";
    LayoutInflater mInflater;
    Context mContext;
    @Override
    public long getItemId(int position) {
        UIConversation conversation = getItem(position);
        if (conversation == null)
            return 0;
        return conversation.hashCode();
    }
    protected class ViewHolder {
        public View layout;
        public View leftImageLayout;
        public View rightImageLayout;
        public View leftUnReadView;
        public View rightUnReadView;
        public AsyncImageView leftImageView;
        public TextView unReadMsgCount;
        public ImageView unReadMsgCountIcon;
        public AsyncImageView rightImageView;
        public TextView unReadMsgCountRight;
        public ImageView unReadMsgCountRightIcon;
        public ProviderContainerView contentView;
    }
    public ConversationListAdapter(Context context) {
        super();
        mContext = context;
        mInflater = LayoutInflater.from(mContext);
    }
    public int findGatheredItem(Conversation.ConversationType type) {
        int index = getCount();
        int position = -1;
        while ((index-- > 0)) {
            UIConversation uiConversation = getItem(index);
            if (uiConversation.getConversationType().equals(type)) {
                position = index;
                break;
            }
        }
        return position;
    }
    public int findPosition(Conversation.ConversationType type, String targetId) {
        int index = getCount();
        int position = -1;
        while (index-- > 0) {
            if (getItem(index).getConversationType().equals(type)
                    && getItem(index).getConversationTargetId().equals(targetId)) {
                position = index;
                break;
            }
        }
        return position;
    }
    @Override
    protected View newView(Context context, int position, ViewGroup group) {
        View result = mInflater.inflate(R.layout.rc_item_conversation, null);
        ViewHolder holder = new ViewHolder();
        holder.layout = findViewById(result, R.id.rc_item_conversation);
        holder.leftImageLayout = findViewById(result, R.id.rc_item1);
        holder.rightImageLayout = findViewById(result, R.id.rc_item2);
        holder.leftUnReadView = findViewById(result, R.id.rc_unread_view_left);
        holder.rightUnReadView = findViewById(result, R.id.rc_unread_view_right);
        holder.leftImageView = findViewById(result, R.id.rc_left);
        holder.rightImageView = findViewById(result, R.id.rc_right);
        holder.contentView = findViewById(result, R.id.rc_content);
        holder.unReadMsgCount = findViewById(result, R.id.rc_unread_message);
        holder.unReadMsgCountRight = findViewById(result, R.id.rc_unread_message_right);
        holder.unReadMsgCountIcon = findViewById(result, R.id.rc_unread_message_icon);
        holder.unReadMsgCountRightIcon = findViewById(result, R.id.rc_unread_message_icon_right);
        result.setTag(holder);
        return result;
    }
    @Override
    protected void bindView(View v, int position, final UIConversation data) {
        ViewHolder holder = (ViewHolder) v.getTag();
        if (data == null) {
            return;
        }
        /*通过会话类型,获得对应的会话provider.ex: PrivateConversationProvider*/
        IContainerItemProvider provider = RongContext.getInstance().getConversationTemplate(data.getConversationType().getName());
        if (provider == null) {
            RLog.e(TAG, "provider is null");
            return;
        }
        View view = holder.contentView.inflate(provider);
        provider.bindView(view, position, data);
        //设置背景色
        if (data.isTop())
            holder.layout.setBackgroundDrawable(mContext.getResources().getDrawable(R.drawable.rc_item_top_list_selector));
        else
            holder.layout.setBackgroundDrawable(mContext.getResources().getDrawable(R.drawable.rc_item_list_selector));
        ConversationProviderTag tag = RongContext.getInstance().getConversationProviderTag(data.getConversationType().getName());
        int defaultId;
        if (data.getConversationType().equals(Conversation.ConversationType.GROUP)) {
            defaultId = R.drawable.rc_default_group_portrait;
        } else if (data.getConversationType().equals(Conversation.ConversationType.DISCUSSION)) {
            defaultId = R.drawable.rc_default_discussion_portrait;
        } else {
            defaultId = R.drawable.rc_default_portrait;
        }
        // 1:图像靠左显示。2:图像靠右显示。3:不显示图像。
        if (tag.portraitPosition() == 1) {
            holder.leftImageLayout.setVisibility(View.VISIBLE);
            holder.leftImageLayout.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mOnPortraitItemClick != null)
                        mOnPortraitItemClick.onPortraitItemClick(v, data);
                }
            });
            holder.leftImageLayout.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    if (mOnPortraitItemClick != null)
                        mOnPortraitItemClick.onPortraitItemLongClick(v, data);
                    return true;
                }
            });
            if (data.getConversationGatherState()) {
                holder.leftImageView.setAvatar(null, defaultId);
            } else {
                if (data.getIconUrl() != null) {
                    holder.leftImageView.setAvatar(data.getIconUrl().toString(), defaultId);
                } else {
                    holder.leftImageView.setAvatar(null, defaultId);
                }
            }
            if (data.getUnReadMessageCount() > 0) {
                holder.unReadMsgCountIcon.setVisibility(View.VISIBLE);
                setUnReadViewLayoutParams(holder.leftUnReadView, data.getUnReadType());
                if (data.getUnReadType().equals(UIConversation.UnreadRemindType.REMIND_WITH_COUNTING)) {
                    if (data.getUnReadMessageCount() > 99) {
                        holder.unReadMsgCount.setText(mContext.getResources().getString(R.string.rc_message_unread_count));
                    } else {
                        holder.unReadMsgCount.setText(Integer.toString(data.getUnReadMessageCount()));
                    }
                    holder.unReadMsgCount.setVisibility(View.VISIBLE);
                    holder.unReadMsgCountIcon.setImageResource(R.drawable.rc_unread_count_bg);
                } else {
                    holder.unReadMsgCount.setVisibility(View.GONE);
                    holder.unReadMsgCountIcon.setImageResource(R.drawable.rc_unread_remind_list_count);
                }
            } else {
                holder.unReadMsgCountIcon.setVisibility(View.GONE);
                holder.unReadMsgCount.setVisibility(View.GONE);
            }
            holder.rightImageLayout.setVisibility(View.GONE);
        } else if (tag.portraitPosition() == 2) {
            holder.rightImageLayout.setVisibility(View.VISIBLE);
            holder.rightImageLayout.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mOnPortraitItemClick != null)
                        mOnPortraitItemClick.onPortraitItemClick(v, data);
                }
            });
            holder.rightImageLayout.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    if (mOnPortraitItemClick != null)
                        mOnPortraitItemClick.onPortraitItemLongClick(v, data);
                    return true;
                }
            });
            if (data.getConversationGatherState()) {
                holder.rightImageView.setAvatar(null, defaultId);
            } else {
                if (data.getIconUrl() != null) {
                    holder.rightImageView.setAvatar(data.getIconUrl().toString(), defaultId);
                } else {
                    holder.rightImageView.setAvatar(null, defaultId);
                }
            }
            if (data.getUnReadMessageCount() > 0) {
                holder.unReadMsgCountRightIcon.setVisibility(View.VISIBLE);
                setUnReadViewLayoutParams(holder.rightUnReadView, data.getUnReadType());
                if (data.getUnReadType().equals(UIConversation.UnreadRemindType.REMIND_WITH_COUNTING)) {
                    holder.unReadMsgCount.setVisibility(View.VISIBLE);
                    if (data.getUnReadMessageCount() > 99) {
                        holder.unReadMsgCountRight.setText(mContext.getResources().getString(R.string.rc_message_unread_count));
                    } else {
                        holder.unReadMsgCountRight.setText(Integer.toString(data.getUnReadMessageCount()));
                    }
                    holder.unReadMsgCountRightIcon.setImageResource(R.drawable.rc_unread_count_bg);
                } else {
                    holder.unReadMsgCount.setVisibility(View.GONE);
                    holder.unReadMsgCountRightIcon.setImageResource(R.drawable.rc_unread_remind_without_count);
                }
            } else {
                holder.unReadMsgCountIcon.setVisibility(View.GONE);
                holder.unReadMsgCount.setVisibility(View.GONE);
            }
            holder.leftImageLayout.setVisibility(View.GONE);
        } else if (tag.portraitPosition() == 3) {
            holder.rightImageLayout.setVisibility(View.GONE);
            holder.leftImageLayout.setVisibility(View.GONE);
        } else {
            throw new IllegalArgumentException("the portrait position is wrong!");
        }
        MessageContent content = data.getMessageContent();
        if (content != null && content.isDestruct()) {
            RongIMClient.getInstance().getMessage(data.getLatestMessageId(), new RongIMClient.ResultCallback<Message>() {
                @Override
                public void onSuccess(Message message) {
                    if (message == null) {
                        EventBus.getDefault().post(new Event.MessageDeleteEvent(data.getLatestMessageId()));
                    }
                }
                @Override
                public void onError(RongIMClient.ErrorCode e) {
                }
            });
        }
    }
    protected void setUnReadViewLayoutParams(View view, UIConversation.UnreadRemindType type) {
        ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
        Context context = view.getContext();
        if (type == UIConversation.UnreadRemindType.REMIND_WITH_COUNTING) {
            params.width = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_18);
            params.height = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_18);
            params.leftMargin = (int) mContext.getResources().getDimension(R.dimen.rc_dimen_size_44);
            params.topMargin = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_5);
        } else {
            params.width = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_9);
            params.height = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_9);
            params.leftMargin = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_50);
            params.topMargin = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_7);
        }
        view.setLayoutParams(params);
    }
    private OnPortraitItemClick mOnPortraitItemClick;
    public interface OnPortraitItemClick {
        void onPortraitItemClick(View v, UIConversation data);
        boolean onPortraitItemLongClick(View v, UIConversation data);
    }
    public void setOnPortraitItemClick(OnPortraitItemClick onPortraitItemClick) {
        this.mOnPortraitItemClick = onPortraitItemClick;
    }
}

集成DefaultExtensionModule, 实现MyExtensionModule, 重写getEmoticonTabs()方法, 为每个图标设置监听.

@Override
    public List<IEmoticonTab> getEmoticonTabs() {
        List<IEmoticonTab> emoticonTabs =  super.getEmoticonTabs();
        MyEmoticonTab myEmoticonTab =new MyEmoticonTab();
        myEmoticonTab.setOnItemClickListener(new IEmojiItemClickListener() {
            @Override
            public void onEmojiClick(String emoji) {
                EditText editText = MyExtensionModule.this.mEditText;
                if (editText != null) {
                    int start = editText.getSelectionStart();
                    editText.getText().insert(start, emoji);
                }
            }
            @Override
            public void onDeleteClick() {
                EditText editText = MyExtensionModule.this.mEditText;
                if (editText != null) {
                    editText.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
                }
            }
        });
        emoticonTabs.add(myEmoticonTab);
        return emoticonTabs;
    }

继承RongExtension, 实现MyRongExtension, 然后获取到editText, 为其设置监听, 代码如下:

public class MyRongExtension extends RongExtension {
    EditText mEditText;
    public MyRongExtension(Context context) {
        super(context);
        tryAddTextChangedAction();
    }
    public MyRongExtension(Context context, AttributeSet attrs) {
        super(context, attrs);
        tryAddTextChangedAction();
    }
    private void tryAddTextChangedAction() {
        mEditText = getInputEditText();
        TextWatcher textWatcher = new TextWatcher() {
            private int start;
            private int count;
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                this.start = start;
                this.count = count;
            }
            @Override
            public void afterTextChanged(Editable s) {
                // 这块的检验规则是纯emoji的, 加其他表情的话直接给他去掉, 或者自己写规则。
//                if (QQEmoji.isQQEmoji(s.subSequence(start, start + count).toString())) {
                    mEditText.removeTextChangedListener(this);
                    String resultStr = QQEmoji.replaceEmojiWithText(s.toString());
                    mEditText.setText(QQEmoji.ensure(resultStr), TextView.BufferType.SPANNABLE);
                    mEditText.setSelection(mEditText.getText().length());
                    mEditText.addTextChangedListener(this);
//                }
            }
        };
        mEditText.addTextChangedListener(textWatcher);
    }
}

这样一来, 就大功告成啦. 添加好了一套属于自己的表情包!

融云即时通讯SDK集成 — 定制UI(三) ——兼容Android Q

技术交流王叫兽 发表了文章 • 0 个评论 • 24 次浏览 • 2020-11-18 11:46 • 来自相关话题

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:官网:... ...查看全部

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4

如何兼容Android Q

Android 10 适配
前言
为了让用户更好地控制自己的文件,并限制文件混乱的情况,Android 10(Q) 修改了 APP 访问外部存储中文件的方法。外部存储的新特性被称为 Scoped Storage。

Android 10(Q) 仍然使用 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 作为面向用户的存储相关运行时权限,但现在即使获取了这些权限,访问外部存储也受到了限制。APP 需要这些运行时权限的情景发生了变化,且各种情况下外部存储对 APP 的可见性也发生了变化 在 Scoped Storage 新特性中,外部存储空间被分为两部分:

● 公共目录:Downloads、Documents、Pictures 、DCIM、Movies、Music、Ringtones 等

公共目录下的文件在 APP 卸载后,不会删除。

APP 可以通过 SAF(System Access Framework)、MediaStore 接口访问其中的文件。

● App-specific 目录

APP 卸载后,数据会清除。

APP 的私密目录,APP 访问自己的 App-specific 目录时无需任何权限。

融云关于 Android 10(Q)适配
之前 SDK 将媒体文件存储于 /sdcard/RongCloud/Media 中,所以在 android 10(Q) 系统上会有聊天无法使用的情况,新版本更新后,会将媒体消息存储于 :sdcard/Android/data/包名/files/ 中,以保证 sdk 功能的正常使用

用户需要做的适配工作
1.如果您是 kit 的用户,我们对融云基本消息类型进行了全面兼容,如果您自定义了媒体消息,建议您在点击自定义媒体消息时,对本地文件进行检查,如果本地文件流无法读取,调用 RongIM 的 downloadMediaMessage() 方法重新下载更新本地路径

示例代码

//1.判断小视频本地文件是否存在if (sightMessage.getLocalPath() != null && !TextUtils.isEmpty(sightMessage.getLocalPath().toString())) {
            return FileUtils.isFileExistsWithUri(this, sightMessage.getLocalPath());
        } else {
            return false;
        }//2.调用 downloadMediaMessage 下载文件刷新 ui  RongIM.getInstance().downloadMediaMessage(mMessage, downloadMediaMessageCallback);

2.如果您是 lib 的用户,建议您在用户使用到媒体类型消息时,对消息体中的 localPath 进行判断检查,如果本地文件流无法正常访问,请调用 RongIMClient 的 downloadMediaMessage() 方法对媒体文件进行重新下载并更新本地路径,具体代买可参考以上代码进行具体调整

备注
文件存储路径变化

不开启 rc_q_storage_mode_enable 的各种文件保存路径//使用sight模块录制小视频时
录制视频:sdcard/sdcard/RongCloud/Image/应用名/image/下载音频 /sdcard/RongCloud/Media
下载视频 /sdcard/RongCloud/Media
下载文件 /sdcard/RongCloud/Media
下载 gif /sdcard/RongCloud/Media
自定义媒体文件 /sdcard/RongCloud/Media
开启 rc_q_storage_mode_enable 的各种文件保存路径//使用sight模块录制小视频时
录制视频:sdcard/Android/data/包名/files/RongCloud/video/下载音频:sdcard/Android/data/包名/files/RongCloud/audio/下载视频:sdcard/Android/data/包名/files/RongCloud/video/下载文件:sdcard/Android/data/包名/files/RongCloud/file/下载 gif:sdcard/Android/data/包名/files/RongCloud/image/自定义媒体文件 会根据媒体类型存储到以上目录中

2.如果您是 lib 的用户,建议您在用户使用到媒体类型消息时,对消息体中的 localPath 进行判断检查,如果本地文件流无法正常访问,请调用 RongIMClient 的 downloadMediaMessage() 方法对媒体文件进行重新下载并更新本地路径,具体代买可参考以上代码进行具体调整

备注
文件存储路径变化

不开启 rc_q_storage_mode_enable 的各种文件保存路径//使用sight模块录制小视频时
录制视频:sdcard/sdcard/RongCloud/Image/应用名/image/下载音频 /sdcard/RongCloud/Media
下载视频 /sdcard/RongCloud/Media
下载文件 /sdcard/RongCloud/Media
下载 gif /sdcard/RongCloud/Media
自定义媒体文件 /sdcard/RongCloud/Media
开启 rc_q_storage_mode_enable 的各种文件保存路径//使用sight模块录制小视频时
录制视频:sdcard/Android/data/包名/files/RongCloud/video/下载音频:sdcard/Android/data/包名/files/RongCloud/audio/下载视频:sdcard/Android/data/包名/files/RongCloud/video/下载文件:sdcard/Android/data/包名/files/RongCloud/file/下载 gif:sdcard/Android/data/包名/files/RongCloud/image/自定义媒体文件 会根据媒体类型存储到以上目录中



融云 IMkit 拦截或监听所有发送消息

技术交流柠檬^ 发表了文章 • 0 个评论 • 20 次浏览 • 2020-11-18 11:38 • 来自相关话题

最近集成融云 IMkit 的 SDK, 有一个需求是要监听所有发出去的消息, 根据消息类型拦截或者进行修改.在官方文档上着了一遍, 都没有找到, 偶然在看 API 文档的时候看见了一个监听然后做了尝试, 是可以满足需求的, 所以再次记录一下.具体的方法是 Ro... ...查看全部

最近集成融云 IMkit 的 SDK, 有一个需求是要监听所有发出去的消息, 根据消息类型拦截或者进行修改.

u=3188089682,3294784426&fm=26&gp=0.jpg

在官方文档上着了一遍, 都没有找到, 偶然在看 API 文档的时候看见了一个监听然后做了尝试, 是可以满足需求的, 所以再次记录一下.

具体的方法是 RongIM 类下的 setSendMessageListener 方法.

代码如下.

   /**
     * 设置发送消息的监听。
     *
     * @param listener 发送消息的监听。
     */
    RongIM.setSendMessageListener(new OnSendMessageListener() {
           @Override
         public Message onSend(Message message) {
                 // 发送消息之前会走此方法. message 为要发送的消息,
                 // 如果返回 null 的话, 就不会发送此消息了.
                 return message;
         }
         @Override
        public boolean onSent(Message message, SentMessageErrorCode sentMessageErrorCode) {
                 发送成功之后会走方法. 返回 true , 就会走 SDK 的后续逻辑. 返回 false 就拦截了.         return true;
             }
    }).


融云即时通讯SDK集成 — 华为推送的点击跳转处理

技术交流王叫兽 发表了文章 • 0 个评论 • 24 次浏览 • 2020-11-18 11:38 • 来自相关话题

1.背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地... ...查看全部

1.背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4
这个任务当然还是落在我的头上. 基础的IM聊天, 群组聊天等功能集成在另一篇博客中已经讲过, 这里就先不说了. 之前的一篇文章已经讲过, 这篇文章重点讲下华为推送过来的通知栏点击事件, 这小小的一件事, 可耗费了不少的精力.

2. 融云家SDK接入厂商推送指南

这个他家讲的挺清楚了, 都近似手把手的教了, 那我也就不在这浪费键盘寿命了(偷个懒hhhhh).给他家官方文档往这儿一贴好了: https://docs.rongcloud.cn/v4/views/im/ui/guide/private/notify/push/mi.html. 大家注意一下他家华为厂商推送的文档分2.x和4.x哈, 对应的是他家旧版/新版SDK中接入的厂商的新/旧版本推送包.我这里之前是踩过坑的, 推荐大家还是用他家的4.x的sdk包集成, 里边集成的是华为新版本的推送包. 因为华为已经在逐渐弃用旧版本的推送包了, 所以不想以后麻烦再迭代的话, 还是直接上新版本比较好, 因为现在已经有接入旧版本包的app在某些低版本华为Rom上收不到推送了, 还是谨慎一点比较好.

3. 华为推送通知点击跳转自定义

这里我们所指的跳转是, 点击通知栏到达的华为推送通知后, 调起我们的app, 跳到相应的Activity的. 因为我们做的是即时通讯App, 所以点击别人发来的消息推送通知后, 当然是跳到和该用户聊天的会话页面, 但就是这样一个小业务, 实现起来还真有点麻烦. 所以这里也算是给大家踩踩坑啦.

首先是点击跳转的逻辑, 通过和融云技术支持联系以及查看他们sdk的代码, 得知他们的推送都是走了一个叫PushMessageReceiver的类, 开发者可以注册并继承该类, 重写onNotificationMessageClicked()方法, 显而易见这就是对通知消息的监听. 可这也是第一个坑, 华为因为平台的限制, 推送通知的点击事件根本不走这里. 准确的说是华为和Oppo都因为各自平台的限制, 不走这里. 所以当我兴致勃勃重写了这个方法准备一次通过的时候, 点击后直接走到了会话列表界面, 而非会话界面.

这是因为华为平台点击跳转是需要配一个intent, 在融云的后台, 如图所示:
QQ20201102-111109@2x.png

这里的intent即为通知栏点击跳转后的隐式启动相应activity的那个intent.

获取某Activity相应intent的代码, 可把intent给打到log中:

Intent intent = new Intent(Intent.ACTION_VIEW,                Uri.parse("wonderfullpush://com.wonderfull.android.push/notification?action=your parameter"));        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);        intent.setAction(Intent.ACTION_VIEW);        String intnetUri = intent.toUri(Intent.URI_INTENT_SCHEME);        Log.d("hwpush", "intnetUri=" + intnetUri);

生成的intent大概是这样的格式:

intent://com.wonderfull.android.push/notification?action=$action#Intent;scheme=wonderfullpush;action=android.intent.action.VIEW;launchFlags=0x10000000;end

不过这块还有坑, 还不能直接把装会话界面的activity的intent直接写在这里, 因为这个会话界面去的究竟是哪个对话者的界面, 相应userId的参数是传不到的. 在这里我尝试获取过intent里边的数据, 是空的.

和融云的技术支持团队确认后, 找到了解决方案. 只有intent走到入口类, 才能拿的到通知里携带的数据, 我app的闪屏页, 也就是SplashActivity, 在融云后台把自定义点击跳转intent配成SplashActivity的. 然后在这里把intent的数据拿到, 再一次调用融云的接口跳转到相应的会话界面就OK了.

等拿到这个intent的后, 又是来了一点点小麻烦. 根据融云的文档, 我在入口Activity(闪屏页, SplashActivity)这里接收到了intent, 从intent里边取相应的数据:

这个intent中给的数据包含了转义符:

{"rc":"{\"conversationType\":\"1\",\"targetId\":\"userid8\",\"sourceType\":\"0\",\"fromUserId\":\"userid8\",\"objectName\":\"RC:TxtMsg\",\"id\":\"BLCG-G8TC-U7E6-KV7P\",\"tId\":\"doctorid3\"}"}
String jsonStr = "{\"rc\":\"{\\\"conversationType\\\":\\\"1\\\",\\\"targetId\\\":\\\"userid8\\\",\\\"sourceType\\\":\\\"0\\\",\\\"fromUserId\\\":\\\"userid8\\\",\\\"objectName\\\":\\\"RC:TxtMsg\\\",\\\"id\\\":\\\"BLCG-G8TC-U7E6-KV7P\\\",\\\"tId\\\":\\\"doctorid3\\\"}\"}";        String fixStr1 = jsonStr.replace("\\", "");        String fixStr2 = fixStr1.replace("\"rc\":\"", "\"rc\":" );        String result = fixStr2.replace("\"}\"", "\"}");        Log.d(TAG, "result jsonStr: "+ result);        JSONObject jsonObject;        try {            jsonObject = new JSONObject(result);            String options = jsonObject.getString("rc");            JsonObject object = (JsonObject) new JsonParser().parse(options);            String targetId = object.getAsJsonObject().get("targetId").getAsString();            Log.d(TAG, "analyse json targetId: " + targetId);        } catch (JSONException e) {            e.printStackTrace();        }

我选择了自行处理字符串, 拿到了相应的targetId, 从我的闪屏页跳转到相应的会话界面去. 这样需求就搞定了.


融云即时通讯SDK集成 — 定制UI(一) ——会话界面小改动

技术交流王叫兽 发表了文章 • 0 个评论 • 28 次浏览 • 2020-11-18 11:38 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4
这个任务当然还是落在我的头上. 我是使用的他们家的带UI的sdk,(他们家有带UI和不带UI的两种sdk, 不带UI的sdk就是只有即时通讯能力, 所有的UI都需要开发者自定实现, 带UI的sdk封装了一些基本的界面,例如会话列表, 和别人聊天的会话界面.)当然这些已经集成了UI的sdk并不能完全满足一个产品的需求, 所以这篇文章主要跟大家讲下如何对他们家的UI进行简单的自定义.

融云SDK接入会话列表/会话界面

大家集成的话可以直接按照他们家的快速集成的步骤走, 包含了集成会话列表以及会话界面. 这里是链接: 快速集成SDK (https://docs.rongcloud.cn/v4/views/im/noui/guide/quick/include/android.html)

接入之后, 可以按照合适的逻辑跳转入这两个基本的界面, 如图所示:
会话列表:
QQ20201102-165916@2x.png会话界面
QQ20201102-165944@2x.png

可以看到我们的app中现在拥有了默认会话列表和会话界面UI, 可以使用基本的功能了. 但是这些基本的UI虽说不上难看, 但是也够不上精美. 所以UI这块还是需要我们自行来做一些定制的.

为了给大家演示, 我这里改的比较夸张了一点, 十分丑陋, 但直观哈哈哈.

3F7310C0-9826-4505-9CE8-56B1EE4427EA.png

如图我直接更改了两边发送者&接收者的字体颜色,字体大小,字体样式. 也更改了双方的聊天气泡.

修改普通文字消息类型的消息, 直接继承了TextMessageItemProvider. 把父类里边所有的代码都复制进来, 然后在bindView()的时候做修改

可以看到我把样式随便改了一下. 两个方向的气泡都改成了箭头左向的.

    @Override    public void bindView(final View v, int position, TextMessage content, final UIMessage data) {        ViewHolder holder = (ViewHolder) v.getTag();        holder.receiverFire.setTag(data.getUId());        if (data.getMessageDirection() == Message.MessageDirection.SEND) {            holder.message.setBackgroundResource(R.drawable.rc_ic_bubble_right);        } else {            holder.message.setBackgroundResource(R.drawable.rc_ic_bubble_left);        }        if (content.isDestruct()) {            bindFireView(v, position, content, data);        } else {            holder.sendFire.setVisibility(View.GONE);            holder.receiverFire.setVisibility(View.GONE);            holder.unRead.setVisibility(View.GONE);            holder.message.setVisibility(View.VISIBLE);            final AutoLinkTextView textView = holder.message;            processTextView(v, position, content, data, textView);        }    }

做完改动, 还需要给这个类添加这样的注解才能绑定TextMessage的渲染:

@ProviderTag(        messageContent = TextMessage.class,        showReadState = true)    public class MyTextMessageItemProvider extends TextMessageItemProvider

然后记得在init 我们SDK之后, 注册一下这个Provider.

    RongIM.init(this, APP_KEY);    RongIM.registerMessageTemplate(new MyTextMessageItemProvider());

这样所有收到的类型为TextMessage的消息, 都会按照我这里定义的TextMessageItemProvider来做展示了. 其他类型的消息也是一样的, 语音, 文件, 位置消息. 只要想改UI, 本质上都是集成相应的MessageItemProvider, 然后重写bindView()方法.


融云如何把图片消息的图片上传到自己的文件服务器

技术交流木土走召 发表了文章 • 0 个评论 • 27 次浏览 • 2020-11-18 11:38 • 来自相关话题

我们使用融云开发的项目, 但我们有一个需求是, 把图片不要上传到融云的服务器, 而是自己的服务器.于是就咨询了一下技术支持.被告知有一个接口方法完全可以满足我们的需求.       Imag... ...查看全部

u=3369131386,464750533&fm=26&gp=0.jpg

我们使用融云开发的项目, 但我们有一个需求是, 把图片不要上传到融云的服务器, 而是自己的服务器.于是就咨询了一下技术支持.

被告知有一个接口方法完全可以满足我们的需求.

       ImageMessage imageMessage = ImageMessage.obtain(Uri.parse(FILEPATH), Uri.parse(FILEPATH));
        configSendMessage(imageMessage);
        Message message = Message.obtain(mTargetId,mConversationType,imageMessage);
        RongIM.getInstance().sendImageMessage(message, "pushcontent", "pushdata",
                new RongIMClient.SendImageMessageWithUploadListenerCallback() {
                    @Override
                    public void onAttached(Message message, RongIMClient.UploadImageStatusListener watcher) {
                            // 这里是自己上传图片的逻辑, 图片的路径可以通过 message 中进行获取. 
                            //watcher 这个参数主要是用于把自己的上传状态同步给 sdk. 这样我们就可以使用 sdk 内部的默认逻辑, 包括界面.
                    }
                    @Override
                    public void onError(Message message, RongIMClient.ErrorCode code) {
                    }
                    @Override
                    public void onSuccess(Message message) {
                    }
                    @Override
                    public void onProgress(Message message, int progress) {
                    }
                });

这样就可以继续使用 IMkit 的界面以及其他逻辑, 只是在 onAttached 中编写我们的上传逻辑并使用 watcher 上传的进度、成功或失败状态同步给 sdk 即可.


友情链接