Developing a Mobile App for IP Camera Surveillance
IP cameras stream video via multiple protocols: RTSP—de facto standard for local networks, HLS/DASH—for cloud solutions, WebRTC—for minimal latency (<500 ms). Mobile surveillance app choice of protocol under task dictates entire architecture.
RTSP: Video Decoding on Mobile
RTSP stream from camera—H.264/H.265 video wrapped in RTP packets. Native RTSP player neither iOS nor Android has. Options:
Android: ExoPlayer with extension-rtsp (Media3 1.0 onwards). Before—only VLC for Android SDK (libvlc-android). ExoPlayer Media3 RTSP works correctly for most H.264 cameras, but with H.265 on some RTSP servers gives SOURCE_ERROR due to non-standard SDP attributes.
class CameraPlayerManager(private val context: Context) {
private var player: ExoPlayer? = null
fun startStream(rtspUrl: String, surfaceView: SurfaceView) {
val mediaItem = MediaItem.Builder()
.setUri(Uri.parse(rtspUrl))
.build()
player = ExoPlayer.Builder(context)
.setMediaSourceFactory(
DefaultMediaSourceFactory(context)
.setLiveTargetOffsetMs(500)
)
.build()
.also { exo ->
exo.setVideoSurfaceView(surfaceView)
exo.setMediaItem(mediaItem)
exo.prepare()
exo.play()
}
}
fun release() {
player?.release()
player = null
}
}
iOS: AVPlayer doesn't support RTSP. Paths: VLCKit (works, ~20 MB binary), FFmpegKit with native FFmpeg compilation (heavier, max flexibility), or custom RTSP client on RTP/UDP via Network.framework. Last option tedious but gives full control over buffering and latency.
ONVIF: Camera Discovery in Network
ONVIF—standard for IP cameras (Hikvision, Dahua, Axis, Reolink). Profile S defines GetStreamUri, GetSnapshotUri, PTZ control. WS-Discovery for device discovery on local network.
// Android: WS-Discovery multicast
class ONVIFDiscovery {
private val MULTICAST_ADDRESS = "239.255.255.250"
private val MULTICAST_PORT = 3702
suspend fun discoverDevices(timeout: Long = 3000): List<ONVIFDevice> {
val socket = MulticastSocket(MULTICAST_PORT)
socket.joinGroup(InetAddress.getByName(MULTICAST_ADDRESS))
socket.soTimeout = timeout.toInt()
val probeMessage = buildWSDiscoveryProbe()
val packet = DatagramPacket(
probeMessage.toByteArray(),
probeMessage.length,
InetAddress.getByName(MULTICAST_ADDRESS),
MULTICAST_PORT
)
socket.send(packet)
val devices = mutableListOf<ONVIFDevice>()
val buffer = ByteArray(4096)
try {
while (true) {
val response = DatagramPacket(buffer, buffer.size)
socket.receive(response)
parseWSDiscoveryResponse(String(response.data, 0, response.length))
?.let { devices.add(it) }
}
} catch (e: SocketTimeoutException) { /* normal exit */ }
socket.close()
return devices
}
}
Get RTSP URL: SOAP GetStreamUri request with WS-Security auth (Digest auth). Library onvif4java simplifies but often lags current firmware—write SOAP client on Retrofit with custom converter easier.
Multi-Camera View
Grid of 4 or 9 cameras simultaneously—heavy task. Each RTSP stream needs own decoder. Android with 9 Full HD cameras risks exhausting hardware decoders (usually 4-8 per chip); rest go to software decoder with FPS drop to 5-10 frames.
Solution: for grid use MJPEG snapshots via ONVIF GetSnapshotUri updated every 2-3 seconds instead of full video. Enable full RTSP on tap—full screen mode. Compromise between load and informativeness.
Recording and History: Local and Cloud Storage
Record clips from camera to phone: download via ONVIF GetRecordings or ISAPI (Hikvision). Local save to MediaStore (Android 10+) or Photos Library (iOS). Cloud cameras—direct MP4 links from manufacturer cloud.
Motion detection: either hardware (in camera, events via ONVIF Event Service) or software on mobile via frame comparison by YUV difference between previous and current. Hardware more reliable—fewer false positives.
Developing mobile app with RTSP view of one camera and basic ONVIF control: 4-5 weeks. Multi-camera system with WS-Discovery, PTZ control, recording history, motion push alerts: 8-12 weeks. Cost individually quoted after camera model analysis and storage requirements.







