Created
May 18, 2025 06:12
-
-
Save flushpot1125/7ef33aba0857168292cef7b7beada5b5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.example.videostream | |
| import android.Manifest | |
| import android.content.pm.PackageManager | |
| import android.os.Bundle | |
| import android.util.Log | |
| import androidx.activity.ComponentActivity | |
| import androidx.activity.compose.setContent | |
| import androidx.activity.enableEdgeToEdge | |
| import androidx.activity.result.contract.ActivityResultContracts | |
| import androidx.compose.foundation.layout.* | |
| import androidx.compose.material3.* | |
| import androidx.compose.runtime.* | |
| import androidx.compose.ui.Alignment | |
| import androidx.compose.ui.Modifier | |
| import androidx.compose.ui.unit.dp | |
| import androidx.core.content.ContextCompat | |
| import androidx.lifecycle.lifecycleScope | |
| import io.ktor.http.ContentType | |
| import io.ktor.http.HttpStatusCode | |
| import io.ktor.server.application.* | |
| import io.ktor.server.engine.* | |
| import io.ktor.server.routing.* | |
| import io.ktor.server.websocket.* | |
| import io.ktor.server.response.respondText | |
| import io.ktor.websocket.WebSocketSession | |
| import kotlinx.coroutines.Dispatchers | |
| import kotlinx.coroutines.launch | |
| import java.net.Inet4Address | |
| import java.net.NetworkInterface | |
| import io.ktor.server.cio.* | |
| import io.ktor.server.response.respond | |
| class MainActivity : ComponentActivity() { | |
| private var server: ApplicationEngine? = null | |
| private var isServerRunning by mutableStateOf(false) | |
| private var isStreamingVideo by mutableStateOf(false) | |
| // カメラ権限のリクエスト | |
| private val requestPermissionLauncher = registerForActivityResult( | |
| ActivityResultContracts.RequestPermission() | |
| ) { isGranted -> | |
| if (isGranted) { | |
| Log.d("Camera", "Permission granted") | |
| } else { | |
| Log.d("Camera", "Permission denied") | |
| } | |
| } | |
| override fun onCreate(savedInstanceState: Bundle?) { | |
| super.onCreate(savedInstanceState) | |
| // カメラ権限を確認 | |
| if (ContextCompat.checkSelfPermission( | |
| this, | |
| Manifest.permission.CAMERA | |
| ) != PackageManager.PERMISSION_GRANTED) { | |
| requestPermissionLauncher.launch(Manifest.permission.CAMERA) | |
| } | |
| enableEdgeToEdge() | |
| setContent { | |
| Surface(modifier = Modifier.fillMaxSize()) { | |
| Column( | |
| modifier = Modifier | |
| .fillMaxSize() | |
| .padding(16.dp), | |
| horizontalAlignment = Alignment.CenterHorizontally, | |
| verticalArrangement = Arrangement.Center | |
| ) { | |
| if (!isServerRunning) { | |
| Button( | |
| onClick = { startServer() }, | |
| modifier = Modifier.padding(8.dp) | |
| ) { | |
| Text("サーバー開始 & 映像伝送開始") | |
| } | |
| } else { | |
| Button( | |
| onClick = { stopServer() }, | |
| colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error), | |
| modifier = Modifier.padding(8.dp) | |
| ) { | |
| Text("映像伝送停止") | |
| } | |
| Spacer(modifier = Modifier.height(16.dp)) | |
| Text("映像配信中: http://${getLocalIpAddress()}:8080") | |
| Text("ブラウザからアクセスしてください") | |
| } | |
| } | |
| } | |
| } | |
| } | |
| private fun startServer() { | |
| lifecycleScope.launch(Dispatchers.IO) { | |
| try { | |
| // Androidで動作するように設定を調整 | |
| val environment = applicationEngineEnvironment { | |
| module { | |
| install(WebSockets) | |
| routing { | |
| // index.htmlのルーティング(既存のもの) | |
| get("/") { | |
| val indexHtml = try { | |
| assets.open("static/index.html").bufferedReader().use { it.readText() } | |
| } catch (e: Exception) { | |
| Log.e("KtorServer", "Error loading index.html", e) | |
| "HTML file not found" | |
| } | |
| call.respondText(indexHtml, ContentType.Text.Html) | |
| } | |
| // client.jsのルーティングを明示的に追加 | |
| get("/client.js") { | |
| try { | |
| val jsContent = assets.open("static/client.js").bufferedReader().use { it.readText() } | |
| call.respondText(jsContent, ContentType.Text.JavaScript) | |
| } catch (e: Exception) { | |
| Log.e("KtorServer", "Error loading client.js", e) | |
| call.respond(HttpStatusCode.NotFound) | |
| } | |
| } | |
| webSocket("/video") { | |
| startVideoStream(this) | |
| } | |
| } | |
| } | |
| // サーバーの設定 | |
| connector { | |
| host = "0.0.0.0" | |
| port = 8080 | |
| } | |
| } | |
| // server = embeddedServer(Netty, environment).start(wait = false) | |
| server = embeddedServer(CIO, environment).start(wait = false) | |
| isServerRunning = true | |
| isStreamingVideo = true | |
| Log.d("KtorServer", "Server started on port 8080") | |
| } catch (e: Exception) { | |
| Log.e("KtorServer", "Error starting server", e) | |
| } | |
| } | |
| } | |
| private fun stopServer() { | |
| lifecycleScope.launch(Dispatchers.IO) { | |
| isStreamingVideo = false | |
| server?.stop(1000, 2000) | |
| server = null | |
| isServerRunning = false | |
| Log.d("KtorServer", "Server stopped") | |
| } | |
| } | |
| private suspend fun startVideoStream(session: WebSocketSession) { | |
| val cameraCapture = CameraCapture(this) | |
| try { | |
| cameraCapture.startCamera(session) | |
| // セッションが閉じるまで待機 | |
| for (frame in session.incoming) { | |
| // クライアントからのメッセージを処理(必要に応じて) | |
| } | |
| } catch (e: Exception) { | |
| Log.e("KtorServer", "Error in video stream", e) | |
| } finally { | |
| cameraCapture.stopCamera() | |
| } | |
| } | |
| private fun getLocalIpAddress(): String { | |
| try { | |
| val en = NetworkInterface.getNetworkInterfaces() | |
| while (en.hasMoreElements()) { | |
| val intf = en.nextElement() | |
| Log.d("Network", "Interface: ${intf.displayName}") | |
| val enumIpAddr = intf.inetAddresses | |
| while (enumIpAddr.hasMoreElements()) { | |
| val inetAddress = enumIpAddr.nextElement() | |
| Log.d("Network", "IP: ${inetAddress.hostAddress}, Loopback: ${inetAddress.isLoopbackAddress}") | |
| if (!inetAddress.isLoopbackAddress && inetAddress is Inet4Address) { | |
| return inetAddress.hostAddress.toString() | |
| } | |
| } | |
| } | |
| } catch (e: Exception) { | |
| Log.e("KtorServer", "Error getting IP address", e) | |
| } | |
| return "Unknown IP" | |
| } | |
| override fun onDestroy() { | |
| stopServer() | |
| super.onDestroy() | |
| } | |
| } | |
| @Composable | |
| fun ServerStatus(ipAddress: String) { | |
| Text("Ktor server running at: http://$ipAddress:8080") | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment