TLDR: I am looking for a way to expose a POST endpoint from an Android app. Is this possible? If so, how?
Long story:
I want to receive data from Arduino devices on my android app. So I want a way to get this data through Wi-Fi (that may be a wrong assumption) but without internet connection. My current idea is to post the data from the Arduino to the smartphone over Wi-Fi.
The thing I don't know, and I didn't find answer yet is: Can I get data send to my hotspot Wi-Fi inside my app?
To host endpoints inside your Android application you will need a sever to serve those endpoints. You can use the NanoHttpD for this.
Check this question to check how to use NanoHttpD in Android.
Thank you to both #CommonsWare and #Taranmeet Singh for helping to find my solution.
I build a small http server on my phone that can be reach through http call from my laptop.
Two points were not so obvious for me :
the tecno
the host ip
For the first point, you can use :
NanoHttpD
AsuncHttpServer ex :
https://github.com/andreivisan/AndroidAsyncHttpServer/blob/master/app/src/main/java/server/http/android/MainActivity.java
Sun http :
https://medium.com/hacktive-devs/creating-a-local-http-server-on-android-49831fbad9ca
Ktor :
https://diamantidis.github.io/2019/11/10/running-an-http-server-on-an-android-app
I choose the last option because :
It used natively kotlin
It is build and maintained by JetBrains, the other library were less maintained
It is really light (10 lines to make it works)
To use Ktor you need to add this in your app gradle :
defaultConfig {
...
multiDexEnabled true
}
For the second point : by default you are bind and localhost, but you can change that :
embeddedServer(Netty, host = "192.168.43.1", port = 8080)
This ip is the default one for Android (it seems), you can also Utils method to get it :
How to get IP address of the device from code?
fun getIPAddress(useIPv4: Boolean): String {
try {
val interfaces: List<NetworkInterface> =
Collections.list(NetworkInterface.getNetworkInterfaces())
for (intf in interfaces) {
val addrs: List<InetAddress> = Collections.list(intf.inetAddresses)
for (addr in addrs) {
if (!addr.isLoopbackAddress) {
val sAddr = addr.hostAddress
//boolean isIPv4 = InetAddressUtils.isIPv4Address(sAddr);
val isIPv4 = sAddr.indexOf(':') < 0
if (useIPv4) {
if (isIPv4) return sAddr
} else {
if (!isIPv4) {
val delim = sAddr.indexOf('%') // drop ip6 zone suffix
return if (delim < 0) sAddr.toUpperCase(Locale.ROOT) else sAddr.substring(
0,
delim
).toUpperCase(Locale.ROOT)
}
}
}
}
}
} catch (ignored: Exception) {
} // for now eat exceptions
return ""
}
This work for me, hope it will help others.
Final code look like this:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
embeddedServer(Netty, host = getIPAddress(true), port = 8080) {
install(ContentNegotiation) {
gson {}
}
routing {
get("/") {
call.respond(mapOf("message" to "Hello , this ktor server"))
}
}
}.start(wait = false)
}
/**
* Get IP address from first non-localhost interface
*
* #param useIPv4 true=return ipv4, false=return ipv6
* #return address or empty string
*/
private fun getIPAddress(useIPv4: Boolean): String {
try {
val interfaces: List<NetworkInterface> =
Collections.list(NetworkInterface.getNetworkInterfaces())
for (intf in interfaces) {
val addrs: List<InetAddress> = Collections.list(intf.inetAddresses)
for (addr in addrs) {
if (!addr.isLoopbackAddress) {
val sAddr = addr.hostAddress
//boolean isIPv4 = InetAddressUtils.isIPv4Address(sAddr);
val isIPv4 = sAddr.indexOf(':') < 0
if (useIPv4) {
if (isIPv4) return sAddr
} else {
if (!isIPv4) {
val delim = sAddr.indexOf('%') // drop ip6 zone suffix
return if (delim < 0) sAddr.toUpperCase(Locale.ROOT) else sAddr.substring(
0,
delim
).toUpperCase(Locale.ROOT)
}
}
}
}
}
} catch (ignored: Exception) {
} // for now eat exceptions
return ""
}}
Related
From what I know already, one socket should handle multiple clients (connections) and one client (connection) should handle send and recive(listen) actions. I can achive this by running a few clients in CLI on the golang server.
package main
import (
"bufio"
"encoding/json"
"fmt"
"log"
"net"
)
var clients = make(map[string]net.Conn) //Connected
var registredClients = make(map[int]net.Conn) //Recognized by id
var messages = make(chan message)
type message struct {
text string
address string
RecipientID int
senderId int
}
type comunicateJson struct {
Mode int `json:"mode"`
SenderID int `json:"senderId"`
RecipientID int `json:"recipientId"`
Message string `json:"message"`
Param1 int `json:"param1"`
Param2 int `json:"param2"`
}
type uRegisterJson struct {
ClientId int `json:"clientId"`
}
func main() {
listen, err := net.Listen("tcp", "XXX.XXX.XXX.XXX:9999")
if err != nil {
log.Fatal(err)
}
go broadcaster()
for {
conn, err := listen.Accept()
if err != nil {
log.Print(err)
continue
}
go handle(conn)
}
}
func handle(conn net.Conn) {
clients[conn.RemoteAddr().String()] = conn
fmt.Println("CONNECT " + conn.RemoteAddr().String())
input := bufio.NewScanner(conn)
for input.Scan() {
fmt.Println("Scan " + conn.RemoteAddr().String())
fmt.Println(input.Text())
messages <- newMessage(input.Text(), conn)
}
//Delete client form map
rmUser(conn)
delete(clients, conn.RemoteAddr().String())
conn.Close() // NOTE: ignoring network errors
}
func isJSON(s string) bool {
var js map[string]interface{}
return json.Unmarshal([]byte(s), &js) == nil
}
func rmUser(con net.Conn) {
for i, s := range registredClients {
if s == con {
delete(registredClients, i)
}
}
}
func newMessage(msg string, conn net.Conn) message {
fmt.Println("Proccces message")
fmt.Println(msg)
if isJSON(msg) {
res := comunicateJson{}
json.Unmarshal([]byte(msg), &res)
res2 := uRegisterJson{}
json.Unmarshal([]byte(msg), &res2)
if res.RecipientID != 0 && res.SenderID != 0 { //NOTE: parssing individual message
addr := conn.RemoteAddr().String()
senderId := res.SenderID
reciverId := res.RecipientID
fmt.Printf("Message from clientId %d to clientId %d\n", res.SenderID, res.RecipientID)
return message{
text: msg,
address: addr,
senderId: senderId,
RecipientID: reciverId,
}
} else if res2.ClientId != 0 { //NOTE: client register
fmt.Printf("New clientId %d\n", res2.ClientId)
registredClients[res2.ClientId] = conn
}
}
addr := conn.RemoteAddr().String()
return message{
text: msg,
address: addr,
}
}
func broadcaster() {
for {
select {
case msg := <-messages:
/*
for i, v := range registredClients {
if msg.RecipientID == i {
fmt.Fprintln(v, msg.text)
}
}
*/
for _, conn := range clients {
if msg.address == conn.RemoteAddr().String() { //NOTE: ignoring sender
continue
}
fmt.Fprintln(conn, msg.text)
}
}
}
}
The interesting part is the commented loop in the broadcaster. Every client who shares his ID (json) can became a registredClient. If the messages recipientId matches the clients id the message is send to that specific client.
My problem is that I cannot build such client in Kotlin. The only way I can send a message is by setting socket.close() and my server has no reason to register such a client. For obvious reasons, I do not want to send a message to every connected clinet.
For sending messages to socket I'am using a kotlin service executed in Fragment/Activity
class SendMessageService: Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
var messageType = intent?.getStringExtra("messageType")
when(messageType) {
"m1" -> messageOne(startId)
"m2" -> messageTwo(startId)
}
return super.onStartCommand(intent, flags, startId);
}
private fun messageOne(startId: Int) {
val jsonObj = JSONObject()
jsonObj.put("mode",1)
jsonObj.put("senderId",getUserId())
jsonObj.put("recipientId",100)
jsonObj.put("message","messageOne")
jsonObj.put("param1",1)
jsonObj.put("param2",2)
CoroutineScope(Dispatchers.IO).launch() {
val socket = Socket(GlobalVars().socketHost, GlobalVars().socketPort)
socket.outputStream.write(jsonObj.toString().toByteArray())
socket.close()
}
stopSelf(startId)
}
private fun messageTwo(startId: Int) {
val jsonObj = JSONObject()
jsonObj.put("mode",1)
jsonObj.put("senderId",getUserId())
jsonObj.put("recipientId",100)
jsonObj.put("message","messageTwo")
jsonObj.put("param1",1)
jsonObj.put("param2",2)
CoroutineScope(Dispatchers.IO).launch() {
val socket = Socket(GlobalVars().socketHost, GlobalVars().socketPort)
socket.outputStream.write(jsonObj.toString().toByteArray())
socket.close()
}
stopSelf(startId)
}
...
}
I don't realy care about the socket.close() here, as long as the message contains the receiverId, the server should know where to deliver it.
For reciving messages from socket am using a Kotlin CoroutineWorker witch is executed in anather Worker.
class SocketWorker(
context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
var notifyid : Int = 0
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
val jsonObj = JSONObject()
jsonObj.put("clientId",getUserId())
try {
var socket = Socket(GlobalVars().socketHost, GlobalVars().socketPort)
PrintWriter(socket.outputStream, true).write(jsonObj.toString()) //This line does not do anything.
//socket.close()
var text = ""
while (true) {
text = BufferedReader(InputStreamReader(socket.inputStream)).readLine()
if (text.contains("recipientId")){
val gson = Gson()
val socketObj: SocketMassage = gson.fromJson(text, SocketMassage::class.java)
if (socketObj.recipientId == getUserId()) { //Socket should be sending to one Client
generateNotification("Title", "Worker")
}
}
}
Result.failure()
} catch (ex: IOException) {
Log.e("felixb", "Socket Error Worker Down")
Result.failure()
}
}
...
}
I am trying here to send a clients id that can be captured by the server and turn the socket thing to listening for new messages. If I close the socket the server will remove the client from the registredClients and there will be no message for that specific client provided by the servers brodcaster commented loop.
Is there any way to send and recive messages using one connection with socket in Kotlin?
Ktor runBlocking new aproach
class SocketWorker(appContext: Context, workerParams: WorkerParameters): Worker(appContext, workerParams) {
var notifyid : Int = 0
#WorkerThread
override fun doWork(): Result {
Log.e("felixb","WORK BEFORE runBlocking")
return runBlocking {
Log.e("felixb","WORK in runBlocking")
val jsonObj = JSONObject()
jsonObj.put("clientId",getUserId())
val selectorManager = SelectorManager(Dispatchers.IO)
val socket = aSocket(selectorManager).tcp().connect(GlobalVars().socketHost, GlobalVars().socketPort)
val receiveChannel = socket.openReadChannel()
val sendChannel = socket.openWriteChannel(autoFlush = true)
launch(Dispatchers.IO) {
while (true) {
val incomingMessage = receiveChannel.readUTF8Line()
if (incomingMessage != null) {
Log.e("felixb",incomingMessage)
} else {
Log.e("felixb","Server closed a connection")
socket.close()
selectorManager.close()
exitProcess(0)
}
}
}
while (true) {
Log.e("felixb","Send message ")
val greeting = jsonObj.toString()
sendChannel.writeStringUtf8(greeting)
}
Result.failure()
}
}
...
}
I've change the doWork body for anather aproach exchanging messages with socket using ktor and runBlocking. The things got creazy in ktor. I've got multiple connection around hundred (server log) and each of them is immidietly closed and after that there are caming multiple messages sended (client log) and never reach the server couz is no connection. Seems the ktor is trying to send a message with no connection. I belive that my ktor client is not compatible with the go server.
android log
In the android log the red text is from messageOne and the white is from messageTwo i think that is a proof that the methods ware executed.
server log
In the server log there are two consoles the lef one is showing what is going on in the server and the right one is the for the go client reciving all messages captured by the server while for now the server is sendig them to every connected client.
The blue arrow shows the go client
The red arrow shows the messageOne android client
The green arrow shows the messagaTwo android client
Add a newline:
socket.outputStream.write((jsonObj.toString()+"\n").toByteArray())
And add a flush().
I´m traying to create app in Android studio thank this app connect to my hosting server and to do any operation with the data.
I have a button that instanciate a kotlin class that contain method to connect DB and function to do operation.
My problem it´s that when i do click in my button my function connect return this message:
I/System.out: java.sql.SQLNonTransientConnectionException: Could not create connection to database server.
i´m using JDBC Driver to connect to my DB, in the future i do a web service to do this.
My function connect have this:
class MySQLDatabaseConnector {
internal var conn: Connection? = null
internal var username = "root" // provide the username
internal var password = "" // provide the corresponding password
fun connect() {
val JDBC_DRIVER = "com.mysql.jdbc.Driver"
val DB_URL = "jdbc:mysql://192.167.1.108:3308/prueba"
val USER = "root"
val PASSWORD = ""
try {
Class.forName(JDBC_DRIVER).newInstance()
conn = DriverManager.getConnection(DB_URL, USER, PASSWORD)
/*ar stmt = conn!!.createStatement()
var resultSet = stmt.executeQuery("SELECT * FROM users")
while(resultSet.next()){
var record = resultSet.getString(1) + " " + resultSet.getString(2) + "\n"
println(record)
}
println(stmt)*/
} catch (ex: SQLException) {
// handle any errors
println(ex.toString())
} catch (ex: Exception) {
// handle any errors
println(ex.toString())
}
}
and my activity main its:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login);
val buttonLogin = findViewById(R.id.btLogin) as Button
buttonLogin.setOnClickListener{
val connect = MySQLDatabaseConnector()
connect.connect()
}
}
i have my connector in lib:
mysql-connector-java-8.0.27.jar
i´m using this. I dowloaded it from this URL with software for windows:
https://dev.mysql.com/downloads/connector/j/5.1.html
i hope that anybody can help me.
Thanks you for readme and sorry for my english
I have a connection delay that I cannot find explanations. Basically the connection to the local machine takes almost 1min in the Emulator and 20s+ on the physical device. I have tried everything I know and ended up with the code below. The time on device is now around 15s, but it is still a lot for just connecting.
Am not doing anything yet, just connecting.
Anyone with idea what I can do to optimize the delay?
class XMPPConnectionManager {
companion object {
const val JABBER_DOMAIN = "localhost"
const val JABBER_URL = "192.168.1.101"
const val JABBER_RESOURCE = "Resource"
const val JABBER_PORT = 5222
const val CONNECTION_TIMEOUT = 300
const val REPLY_TIMEOUT = 30000L
}
private val conn: XMPPTCPConnection by lazy {
val builder = XMPPTCPConnectionConfiguration.builder()
.setXmppDomain(JidCreate.domainBareFrom(JABBER_DOMAIN))
.setHostAddress(InetSocketAddress(JABBER_URL, JABBER_PORT).address)
.setResource(JABBER_RESOURCE)
.setCompressionEnabled(true)
.setSendPresence(false)
.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled) //TODO: Enable it
//.enableDefaultDebugger()
.setConnectTimeout(CONNECTION_TIMEOUT)
val connection = XMPPTCPConnection(builder.build())
connection.replyTimeout = REPLY_TIMEOUT
//auto reconnection
val reconnectionManager = ReconnectionManager.getInstanceFor(connection)
reconnectionManager.enableAutomaticReconnection()
reconnectionManager.setReconnectionPolicy(ReconnectionManager.ReconnectionPolicy.RANDOM_INCREASING_DELAY)
connection.connect()
return#lazy connection
}
fun login(username: String?, password: String?): Boolean {
try {
if (!conn.isConnected) {
conn.connect()
}
if(!conn.isAuthenticated){
conn.login(username, password)
//Setup OX-IM and other listeners
}
} catch (e: Exception) {
return false
}
return true
}
}
In swift there is a wonderful thing
if UIApplication.shared.canOpenURL(myUrl) {
// url can be opened
UIApplication.shared.open(myUrl) { success in
if !success {
//...
}
}
} else {
// and cannot
}
Is there an analogue in Kotlin?
Going off the documentation for canOpenURL(), it doesn't check if the URL is available, only if there's an app available that can handle its scheme.
On Android, the URL has to be wrapped in an Intent to be able to open it. You can then check if an app is available for the Intent by using the PackageManager.
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(url)
}
if (intent.resolveActivity(packageManager) != null) {
// url can be opened with startActivity(intent) or requireContext().startActivity(intent)
} else {
// ...
}
If this function is in a Fragment rather than Activity, prefix packageManager with requireContext()..
Edit:
You can check if it's possible to connect to the URL using a function like this (adapted from here):
suspend fun canConnect(url: String): Boolean = withContext(Dispatchers.IO) {
// We want to check the current URL
HttpURLConnection.setFollowRedirects(false)
val httpURLConnection = (URL(url).openConnection() as HttpURLConnection)
// We don't need to get data
httpURLConnection.requestMethod = "HEAD"
// Some websites don't like programmatic access so pretend to be a browser
httpURLConnection.setRequestProperty(
"User-Agent",
"Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)"
)
// We only accept response code 200
return#withContext try {
httpURLConnection.responseCode == HttpURLConnection.HTTP_OK
} catch (e: IOException) {
false
} catch (e: UnknownHostException){
false
}
}
It has to be done asynchronously since you're making a connection, or else you risk an Application Not Responding error. So I made it a suspend function that you can call from a coroutine.
You can check if a URL is valid or not using patterns. See the sample function:
fun isValidUrl(url: String): Boolean {
val p = Patterns.WEB_URL
val m = p.matcher(url)
return m.matches()
}
Once the URL is validated, you can verify whether the device is able to connect to the URL or not using below method:
fun isAPIAvailable(c: Context, url:String): Boolean {
return try {
val ipAddr: InetAddress = InetAddress.getByName(url)
ipAddr.hostAddress != ""
} catch (e: Exception) {
false
}
}
Add isValidUrl():
fun String.isValidUrl(): Boolean = Patterns.WEB_URL.matcher(this).matches()
&& URLUtil.isValidUrl(url)
Then check:
val url = "www.myWebpage.com"
if (!url.isValidUrl()) {
// url can be opened
}else{
// and cannot
}
I am trying to stream video from Raspberry Pi to android device via webrtc. I am using firebase (firestore) as signalling. I am able to run the setup while connected to same wifi but it fails when different networks are being used.
Device - RPI
Client
1) Web client (hosted on firebase)
2) Android App
On same network (wifi) between device and clients, both clients are able to play video and audio.
But when device and client are on different network, web client is able to show video but Android App is not able to show video.
Signalling is working correctly and on device, camera and microphone are started and ice candidates are exchanged successfully. I also get remote stream added (onAddStream called) on android. But no video and audio is playing.
Android PeerConnectionClient
class PeerConnectionClient(private val activity: MainActivity, private val fSignalling: FSignalling) {
internal var isVideoRunning = false
private val rootEglBase by lazy {
EglBase.create()
}
private val peerConnectionFactory: PeerConnectionFactory by lazy {
val initializationOptions = PeerConnectionFactory.InitializationOptions.builder(activity).createInitializationOptions()
PeerConnectionFactory.initialize(initializationOptions)
val options = PeerConnectionFactory.Options()
val defaultVideoEncoderFactory = DefaultVideoEncoderFactory(rootEglBase.eglBaseContext, true, true)
val defaultVideoDecoderFactory = DefaultVideoDecoderFactory(rootEglBase.eglBaseContext)
PeerConnectionFactory.builder()
.setOptions(options)
.setVideoEncoderFactory(defaultVideoEncoderFactory)
.setVideoDecoderFactory(defaultVideoDecoderFactory)
.createPeerConnectionFactory()
}
private val iceServersList = mutableListOf("stun:stun.l.google.com:19302")
private var sdpConstraints: MediaConstraints? = null
private var localAudioTrack: AudioTrack? = null
private var localPeer: PeerConnection? = null
private var gotUserMedia: Boolean = false
private var peerIceServers: MutableList<PeerConnection.IceServer> = ArrayList()
init {
peerIceServers.add(PeerConnection.IceServer.builder(iceServersList).createIceServer())
// activity.surface_view.release()
activity.surface_view.init(rootEglBase.eglBaseContext, null)
activity.surface_view.setZOrderMediaOverlay(true)
createPeer()
}
private fun createPeer() {
sdpConstraints = MediaConstraints()
val audioconstraints = MediaConstraints()
val audioSource = peerConnectionFactory.createAudioSource(audioconstraints)
localAudioTrack = peerConnectionFactory.createAudioTrack("101", audioSource)
gotUserMedia = true
activity.runOnUiThread {
if (localAudioTrack != null) {
createPeerConnection()
// doCall()
}
}
}
/**
* Creating the local peerconnection instance
*/
private fun createPeerConnection() {
val constraints = MediaConstraints()
constraints.mandatory.add(MediaConstraints.KeyValuePair("offerToReceiveAudio", "true"))
constraints.mandatory.add(MediaConstraints.KeyValuePair("offerToReceiveVideo", "true"))
constraints.optional.add(MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"))
val rtcConfig = PeerConnection.RTCConfiguration(peerIceServers)
// TCP candidates are only useful when connecting to a server that supports
// ICE-TCP.
rtcConfig.enableDtlsSrtp = true
rtcConfig.enableRtpDataChannel = true
// rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED
// rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE
// rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE
// rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY
// Use ECDSA encryption.
// rtcConfig.keyType = PeerConnection.KeyType.ECDSA
localPeer = peerConnectionFactory.createPeerConnection(rtcConfig, constraints, object : PeerObserver {
override fun onIceCandidate(p0: IceCandidate) {
super.onIceCandidate(p0)
onIceCandidateReceived(p0)
}
override fun onAddStream(p0: MediaStream) {
activity.showToast("Received Remote stream")
super.onAddStream(p0)
gotRemoteStream(p0)
}
})
addStreamToLocalPeer()
}
/**
* Adding the stream to the localpeer
*/
private fun addStreamToLocalPeer() {
//creating local mediastream
val stream = peerConnectionFactory.createLocalMediaStream("102")
stream.addTrack(localAudioTrack)
localPeer!!.addStream(stream)
}
/**
* This method is called when the app is initiator - We generate the offer and send it over through socket
* to remote peer
*/
/*private fun doCall() {
localPeer!!.createOffer(object : mySdpObserver {
override fun onCreateSuccess(p0: SessionDescription) {
super.onCreateSuccess(p0)
localPeer!!.setLocalDescription(object: mySdpObserver {}, p0)
Log.d("onCreateSuccess", "SignallingClient emit ")
}
}, sdpConstraints)
}*/
private fun onIceCandidateReceived(iceCandidate: IceCandidate) {
//we have received ice candidate. We can set it to the other peer.
if (localPeer == null) {
return
}
val message = JSONObject()
message.put("type", "candidate")
message.put("label", iceCandidate.sdpMLineIndex)
message.put("id", iceCandidate.sdpMid)
message.put("candidate", iceCandidate.serverUrl)
fSignalling.doSignalingSend(message.toString())
}
private fun gotRemoteStream(stream: MediaStream) {
isVideoRunning = true
//we have remote video stream. add to the renderer.
val videoTrack = stream.videoTracks[0]
videoTrack.setEnabled(true)
activity.runOnUiThread {
try {
// val remoteRenderer = VideoRenderer(surface_view)
activity.surface_view.visibility = View.VISIBLE
// videoTrack.addRenderer(remoteRenderer)
videoTrack.addSink(activity.surface_view)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun onReceivePeerMessage(data: JSONObject) {
if (data.getString("type") == "offer") {
// val sdpReturned = SdpUtils.forceChosenVideoCodec(data.getString("sdp"), "H264")
val sdpReturned = data.getString("sdp")
// data.remove("sdp")
// data.put("sdp", sdpReturned)
val sessionDescription = SessionDescription(SessionDescription.Type.OFFER, sdpReturned)
localPeer?.setRemoteDescription(object: mySdpObserver { }, sessionDescription)
localPeer?.createAnswer(object : mySdpObserver {
override fun onCreateSuccess(p0: SessionDescription) {
super.onCreateSuccess(p0)
localPeer!!.setLocalDescription( object : mySdpObserver {}, p0)
val description = JSONObject()
description.put("type", p0.type.canonicalForm())
description.put("sdp", p0.description)
this#PeerConnectionClient.fSignalling.doSignalingSend(description.toString())
}
override fun onCreateFailure(p0: String) {
super.onCreateFailure(p0)
activity.showToast("Failed to create answer")
}
}, MediaConstraints())
} else if (data.getString("type") == "candidate") {
val iceCandidates = IceCandidate(data.getString("id"), data.getInt("label"), data.getString("candidate"))
localPeer?.addIceCandidate(iceCandidates)
}
}
internal fun close() {
isVideoRunning = false
localPeer?.close()
localPeer = null
}
}
I am under the impression that if web client is able to display video on different network (mobile hotspot), android client on same internet used by web client should be able to display video as well. Is it wrong?
Why won't android display video (onAddStream is called)
Is it required to use Turn server? My assumption again is the if web client works, so should android. The service i am using on RPI do not have support for turn server.
Additional info:
Device is behind double natted ISP (i guess) (but since web client can connect, it won't be an issue i guess).
I have found a solution to the issue
I was using
private fun onIceCandidateReceived(iceCandidate: IceCandidate) {
//we have received ice candidate. We can set it to the other peer.
if (localPeer == null) {
return
}
val message = JSONObject()
message.put("type", "candidate")
message.put("label", iceCandidate.sdpMLineIndex)
message.put("id", iceCandidate.sdpMid)
message.put("candidate", iceCandidate.serverUrl)
fSignalling.doSignalingSend(message.toString())
}
Instead was required to use
message.put("candidate", iceCandidate.sdp) // iceCandidate.serverUrl)