实时通信与图像处理应用开发指南
1. 构建实时通信应用 - Pumpidu
在开发实时通信应用时,我们需要处理不同类型的消息。当接收到
offer
类型的消息时,意味着这是一个呼入电话。我们会使用消息内容创建
SessionDescription
,将其设置为对等连接的远程描述,并创建一个应答。如果是
answer
类型的消息,我们执行相同的操作,但不创建应答。对于
candidate
消息类型,我们创建
IceCandidate
并将其添加到对等连接中。每个媒体流都有一个标签,例如
Gk7EuLhsuPTbnjFGkR7xPPJK8ONsgwFFvRS
。
以下是处理消息的逻辑流程:
graph TD;
A[接收消息] --> B{消息类型};
B -->|offer| C[创建SessionDescription并设置为远程描述,创建应答];
B -->|answer| D[创建SessionDescription并设置为远程描述];
B -->|candidate| E[创建IceCandidate并添加到对等连接];
在移动应用方面,为了让移动设备能够访问摄像头和麦克风,我们需要在
platforms/android/AndroidManifest.xml
文件中添加以下代码:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
然而,即使添加了这些权限,移动设备的音频可能仍然无法正常传输。这时可以使用
org.chromium.audiocapture
插件,该插件通过
getUserMedia
API 设置从设备麦克风捕获音频的权限。可以使用以下命令安装该插件:
$ cordova plugin add org.chromium.audiocapture
应用开发完成后,我们需要启动各个部分并尝试进行通话:
1. 启动信令服务器:
$ cd server
$ node server.js
- 在浏览器中启动客户端:
$ cd client/www
$ python -m SimpleHTTPServer 8000
- 在真实设备上启动移动应用:
$ cd client/www
$ cordova run android
当我们在浏览器中打开
http://localhost:8000
时,会被要求授予对摄像头和麦克风的访问权限。点击“允许”后,我们将看到相关界面。在移动设备上也会看到类似的界面,但不会要求访问摄像头和麦克风。当我们在移动应用或浏览器中点击“呼叫”按钮时,两端之间将建立通话,视频和音频都能正常工作。
2. 使用 PeerJS 构建实时通信应用
PeerJS
是浏览器 WebRTC 实现的包装器,旨在简化对等连接管理,并提供列出已连接客户端的功能。
2.1 服务器端
我们将创建一个简单的 Node.js 应用,并添加
peer
和
ip
模块。可以使用以下命令进行安装:
$ npm install peer --save
$ npm install ip --save
安装完成后,
package.json
文件可能如下所示:
{
"name": "pumpidu-peerjs",
"version": "0.0.0",
"description": "Run a PeerJS WebRTC server",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Andrew Kovalenko <cybind@gmail.com>",
"license": "MIT",
"dependencies": {
"peer": "^0.2.5",
"ip": "^0.3.0"
}
}
创建
server.js
文件后,文件夹结构如下:
.
├── node_modules
│ ├── ip
│ └── peer
├── package.json
└── server.js
在
server.js
中添加以下逻辑:
var ip = require('ip');
var port = 9000;
var PeerServer = require('peer').PeerServer;
var server = new PeerServer({
port: port,
allow_discovery: true
});
server.on('connection', function(id) {
console.log('new connection with id ' + id);
});
server.on('disconnect', function(id) {
console.log('disconnect with id ' + id);
});
console.log('peer server running on ' + ip.address() + ':' + port);
可以使用以下命令启动服务器:
$ node server.js
也可以使用更简单的方式在命令行中启动
PeerJS
服务器:
$ peerjs --port 9000 --key peerjs
2.2 客户端
我们保持 CSS 和 HTML 与之前的示例相同,只将
socket.io
库替换为
peer.js
:
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="js/peer.js"></script>
<script type="text/javascript" src="js/index.js"></script>
在
index.js
的
init()
函数中,我们定义以下变量:
var SERVER_IP = '192.168.0.102';
var SERVER_PORT = 9000;
var callButton = document.querySelector("#callButton");
var localVideo = document.querySelector("#localVideo");
var remoteVideo = document.querySelector("#remoteVideo");
var callerId = null;
var peer = null;
var localStream = null;
这些变量的含义如下:
| 变量名 | 含义 |
| ---- | ---- |
|
SERVER_IP
和
SERVER_PORT
|
PeerJS
服务器的位置 |
|
callButton
、
localVideo
和
remoteVideo
| 用户与应用交互时操作的 DOM 元素 |
|
callerId
| 当前客户端设置的 ID |
|
peer
| 当前客户端使用其
callerId
连接时实例化的
PeerJS
对象 |
|
localStream
| 使用
getUserMedia()
捕获的本地视频流 |
应用启动后,会显示一个提示窗口,要求输入
callerId
(昵称):
var setCallerId = function () {
callerId = prompt('Please enter your name');
connect();
};
connect()
函数会检查
callerId
是否设置,如果未设置则会再次提示输入。然后尝试连接到
PeerJS
服务器:
try {
console.log('create connection to the ID server');
console.log('host: ' + SERVER_IP + ', port: ' + SERVER_PORT);
peer = new Peer(callerId, {
host: SERVER_IP,
port: SERVER_PORT
});
// ...
} catch (e) {
peer = null;
alert('Error while connecting to server');
}
连接成功后,会分配
onclose
、
onopen
和
on call
处理程序:
peer.socket._socket.onclose = function() {
alert('No connection to server');
peer = null;
};
peer.socket._socket.onopen = function() {
getLocalStream(function() {
callButton.style.display = 'block';
});
};
peer.on('call', answer);
getLocalStream
函数用于获取本地视频和音频流:
var getLocalStream = function(successCb) {
if (localStream && successCb) {
successCb(localStream);
} else {
navigator.webkitGetUserMedia({
audio: true,
video: true
},
function(stream) {
localStream = stream;
localVideo.src = window.URL.createObjectURL(stream);
if (successCb) {
successCb(stream);
}
},
function(err) {
alert('Failed to access local camera');
console.log(err);
});
}
};
当接收到呼入电话时,会执行
answer
函数:
var answer = function(call) {
if (!peer) {
alert('Cannot answer a call without a connection');
return;
}
if (!localStream) {
alert('Could not answer call as there is no localStream ready');
return;
}
console.log('Incoming call answered');
call.on('stream', showRemoteStream);
call.answer(localStream);
};
var showRemoteStream = function(stream) {
remoteVideo.src = window.URL.createObjectURL(stream);
};
对于呼出电话,我们将
dial()
函数附加为呼叫按钮点击的事件处理程序:
callButton.addEventListener('click', dial);
var dial = function() {
if (!peer) {
alert('Please connect first');
return;
}
if (!localStream) {
alert('Could not start call as there is no local camera');
return;
}
var recipientId = prompt('Please enter recipient name');
if (!recipientId) {
alert('Could not start call as no recipient ID is set');
dial();
return;
}
getLocalStream(function(stream) {
console.log('Outgoing call initiated');
var call = peer.call(recipientId, stream);
call.on('stream', showRemoteStream);
call.on('error', function(e) {
alert('Error with call');
console.log(e.message);
});
});
};
运行应用的步骤与之前的应用相同:
1. 启动信令服务器:
$ cd server
$ node server.js
- 在浏览器中启动客户端:
$ cd client/www
$ python -m SimpleHTTPServer 8000
- 在真实设备上启动移动应用:
$ cd client/www
$ cordova run android
打开浏览器并访问
http://localhost:8000
,输入
client ID
和授予摄像头和麦克风权限后,就可以进行通话。在移动应用中也执行相同的操作,点击“呼叫”按钮,输入对方的昵称,即可发起通话。
3. 探索其他构建 WebRTC 移动应用的工具
目前开发的 WebRTC 应用仅适用于桌面浏览器和 Android。由于 Web 技术在浏览器中的实现比在
WebView
中更好,希望 iOS 的
WebView
能尽快支持 WebRTC,或者能够在 iOS 上使用
Crosswalk
。以下介绍几种可用的解决方案:
-
OpenTok
:这是一个复杂的付费解决方案,可帮助在移动应用或网站中添加视频/音频通话功能。可以在其网站
https://tokbox.com/
上查看详细描述和价格。该服务提供
STUN
和
TURN
功能,无需自己设置服务器,资费基于通话时长。还有对应的
Cordova
插件,可在
https://github.com/songz/cordova-plugin-opentok
找到。Android 安装相对简单,iOS 配置则需要一些努力。
-
PhoneRTC
:这是一个免费的
PhoneGap/Cordova
插件,更多信息可在
http://phonertc.io/
查看。Android 安装没有问题,但 iOS 设置可能会遇到一些困难。还可以了解
PhoneRTC
插件与
SIP.js
的配合使用,相关插件可在
https://github.com/onsip/onsip-cordova
找到。
在
OpenTok
和
PhoneRTC
中,
Cordova
插件都使用 Android 和 iOS 的 WebRTC 原生实现,不通过
WebView
。可以在以下链接查看原生 WebRTC 代码和用法:
-
http://www.webrtc.org/native-code/android
-
http://www.webrtc.org/native-code/ios
4. 构建具有 Instagram 风格图像滤镜的应用 - “Imaginary”
在这个项目中,我们将开发一个类似 Instagram 的应用,用于对图片应用效果。具体包括:
- 重新审视使用 Sencha Touch 进行应用结构组织
- 理解使用 HTML5 Canvas 和 Pixastic 库进行图像处理
- 使用 HTML5 Canvas 调整图像大小
- 为 iOS 构建自定义的 PhoneGap/Cordova 插件
4.1 Pixastic 库概述
为了轻松地对从设备相机获取的图片应用图像滤镜,我们可以使用
Pixastic
库。该库可以从 GitHub 仓库
https://github.com/jseidelin/pixastic
下载最新的库文件。它使用 HTML5 Canvas 进行图像处理,这里选择使用 Web 工作者的场景,因为 Web 工作者是在后台运行的 JavaScript 代码,不会阻塞网页,也不会影响页面性能,非常适合移动设备。使用该库需要四个主要文件:
-
pixastic.js
:包含基本逻辑的主文件
-
pixastic.effects.js
:影响逻辑和像素处理的文件
-
pixastic.worker.control.js
:用于控制工作者的文件
-
pixastic.worker.js
:工作者文件本身
以下是
Pixastic
库的简单使用示例:
var img = new Image();
img.onload = function () {
var oc = document.createElement('canvas'),
octx = oc.getContext('2d');
oc.width = img.width;
oc.height = img.height;
octx.drawImage(img, 0, 0, img.width, img.height);
P = new Pixastic(octx);
P['mosaic']({ blockSize: 8 }).done(function() {
// processing finished
var data = oc.toDataURL();
}, function(p) {
// display progress here;
});
};
img.src = 'some/image/url/image.jpg';
上述代码的执行步骤如下:
1. 创建
Image
对象并分配源,等待图片加载。
2. 创建一个
canvas
元素并获取其 2D 上下文。
3. 将图片的宽度和高度分配给
canvas
。
4. 使用
drawImage()
函数将图像绘制到
canvas
上。
5. 创建
Pixastic
对象并传入
canvas
的上下文。
6. 处理
mosaic
效果,设置效果选项,并提供成功和失败回调。
7. 处理完成后,使用
oc.toDataURL()
获取带有效果的图片的 Base64 编码数据。
通过以上步骤,我们可以使用
Pixastic
库对图片应用各种效果,为开发类似 Instagram 的应用奠定基础。后续我们将继续深入开发 “Imaginary” 应用,实现更多功能。
实时通信与图像处理应用开发指南
5. “Imaginary” 应用开发详细流程
5.1 应用结构组织
在使用 Sencha Touch 进行应用结构组织时,我们需要遵循其 MVC(Model - View - Controller)架构模式。以下是大致的组织步骤:
1.
定义模型(Model)
:确定应用中数据的结构和行为。例如,如果我们要存储图片的相关信息,如图片的原始路径、处理后的路径、应用的滤镜效果等,可以创建一个图片模型。
Ext.define('Imaginary.model.Picture', {
extend: 'Ext.data.Model',
fields: [
{ name: 'originalPath', type: 'string' },
{ name: 'processedPath', type: 'string' },
{ name: 'filterEffect', type: 'string' }
]
});
- 创建视图(View) :设计用户界面,包括图片展示区域、滤镜选择按钮等。
Ext.define('Imaginary.view.Main', {
extend: 'Ext.Container',
xtype: 'mainview',
config: {
layout: 'vbox',
items: [
{
xtype: 'image',
itemId: 'pictureDisplay',
src: '',
flex: 1
},
{
xtype: 'button',
text: 'Apply Filter',
handler: function() {
// 处理滤镜应用逻辑
}
}
]
}
});
- 设置控制器(Controller) :处理视图和模型之间的交互,如响应用户点击按钮事件,调用 Pixastic 库进行图像处理。
Ext.define('Imaginary.controller.Main', {
extend: 'Ext.app.Controller',
config: {
refs: {
mainView: 'mainview',
pictureDisplay: 'mainview #pictureDisplay'
},
control: {
'mainview button[text="Apply Filter"]': {
tap: 'onApplyFilter'
}
}
},
onApplyFilter: function() {
// 获取图片元素
var picture = this.getPictureDisplay();
var img = new Image();
img.src = picture.getSrc();
img.onload = function () {
var oc = document.createElement('canvas'),
octx = oc.getContext('2d');
oc.width = img.width;
oc.height = img.height;
octx.drawImage(img, 0, 0, img.width, img.height);
P = new Pixastic(octx);
P['mosaic']({ blockSize: 8 }).done(function() {
var data = oc.toDataURL();
picture.setSrc(data);
}, function(p) {
// 显示处理进度
});
};
}
});
5.2 图像处理流程
使用 HTML5 Canvas 和 Pixastic 库进行图像处理的详细流程如下:
graph TD;
A[选择图片] --> B[加载图片到 Image 对象];
B --> C[创建 Canvas 元素并获取 2D 上下文];
C --> D[将图片绘制到 Canvas 上];
D --> E[创建 Pixastic 对象并传入 Canvas 上下文];
E --> F[选择滤镜效果并设置参数];
F --> G[处理滤镜效果];
G --> H[获取处理后的图片数据];
H --> I[更新图片显示];
5.3 图像调整大小
使用 HTML5 Canvas 调整图像大小的代码示例如下:
function resizeImage(img, newWidth, newHeight) {
var canvas = document.createElement('canvas');
canvas.width = newWidth;
canvas.height = newHeight;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, newWidth, newHeight);
return canvas.toDataURL();
}
// 使用示例
var img = new Image();
img.src = 'original/image/path.jpg';
img.onload = function() {
var resizedData = resizeImage(img, 300, 200);
// 使用 resizedData 更新图片显示
};
5.4 为 iOS 构建自定义 PhoneGap/Cordova 插件
为 iOS 构建自定义插件的步骤如下:
1.
创建插件目录结构
:在项目根目录下创建一个新的插件目录,例如
plugins/com.example.imaginaryfilter
。
2.
编写插件代码
:在插件目录下创建
src/ios
目录,编写 Objective - C 代码实现滤镜处理逻辑。
#import <Cordova/CDV.h>
@interface ImaginaryFilterPlugin : CDVPlugin
- (void)applyFilter:(CDVInvokedUrlCommand*)command;
@end
@implementation ImaginaryFilterPlugin
- (void)applyFilter:(CDVInvokedUrlCommand*)command {
// 处理滤镜逻辑
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"Filter applied successfully"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
@end
-
创建插件配置文件
:在插件目录下创建
plugin.xml文件,配置插件信息。
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
id="com.example.imaginaryfilter"
version="1.0.0">
<name>Imaginary Filter Plugin</name>
<description>Apply filters to images</description>
<license>MIT</license>
<engines>
<engine name="cordova" version=">=3.0.0" />
</engines>
<js-module src="www/ImaginaryFilter.js" name="ImaginaryFilter">
<clobbers target="cordova.plugins.ImaginaryFilter" />
</js-module>
<platform name="ios">
<config-file target="config.xml" parent="/*">
<feature name="ImaginaryFilter">
<param name="ios-package" value="ImaginaryFilterPlugin" />
</feature>
</config-file>
<source-file src="src/ios/ImaginaryFilterPlugin.m" />
</platform>
</plugin>
-
编写 JavaScript 接口
:在
www目录下创建ImaginaryFilter.js文件,提供 JavaScript 调用接口。
var exec = require('cordova/exec');
var ImaginaryFilter = {
applyFilter: function(successCallback, errorCallback) {
exec(successCallback, errorCallback, 'ImaginaryFilter', 'applyFilter', []);
}
};
module.exports = ImaginaryFilter;
- 安装插件 :在项目根目录下使用以下命令安装插件。
$ cordova plugin add plugins/com.example.imaginaryfilter
6. 总结与展望
通过以上步骤,我们成功开发了实时通信应用和具有 Instagram 风格图像滤镜的应用。在实时通信应用开发中,我们使用了不同的方法,包括直接使用 WebRTC 和借助 PeerJS 库,同时还介绍了一些其他构建 WebRTC 移动应用的工具。在图像处理应用开发中,我们深入了解了 Pixastic 库的使用,以及如何使用 Sencha Touch 组织应用结构、使用 HTML5 Canvas 进行图像处理和调整大小,还为 iOS 构建了自定义插件。
未来,我们可以进一步优化这些应用。例如,在实时通信应用中,可以添加更多的功能,如屏幕共享、文件传输等。在图像处理应用中,可以扩展滤镜效果,支持更多的图像格式,提高处理性能。同时,随着技术的不断发展,我们期待 iOS 的 WebView 能够更好地支持 WebRTC,为开发跨平台的实时通信应用提供更多便利。
总之,实时通信和图像处理应用开发是一个充满挑战和机遇的领域,我们可以不断探索和创新,开发出更强大、更实用的应用。
超级会员免费看
6万+

被折叠的 条评论
为什么被折叠?



