728x90
실행결과입니다.
전체 코드입니다.
메니페스트에 아래의 코드를 추가하시면 됩니다.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
아래의 코드는 application 안에 추가하세요
<service
android:name=".MyService"
android:foregroundServiceType="mediaProjection"
/>
MainActivity.java 코드입니다.
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.Intent;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import org.webrtc.EglBase;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RendererCommon;
import org.webrtc.ScreenCapturerAndroid;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
public class MainActivity extends AppCompatActivity {
Button 알림, 중지;
final String VIDEO_TRACK_ID = "ARDAMSs0";
final int REQUEST_CODE_SCREEN_CAPTURE = 1;
EglBase rootEglBase;
EglBase.Context eglBaseContext;
SurfaceTextureHelper surfaceTextureHelper;
Handler handler = new Handler();
SurfaceViewRenderer renderer;
VideoCapturer videoCapturer;
PeerConnectionFactory peerConnectionFactory;
VideoSource videoSource;
MediaProjectionManager mediaProjectionManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rootEglBase= EglBase.create();
eglBaseContext= rootEglBase.getEglBaseContext();
surfaceTextureHelper= SurfaceTextureHelper.create(Thread.currentThread().getName(), eglBaseContext);
renderer = findViewById(R.id.remoteview1);
// MediaProjectionManager : 화면 캡처나 화면 녹화와 같은 기능을 수행할 수 있습니다.
// getSystemService(Context.MEDIA_PROJECTION_SERVICE) : 안드로이드에서 제공하는 시스템 서비스 중 하나인 MediaProjectionManager를 가져오기 위한 메서드
mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
// PeerConnectionFactory 초기화 및 객체 생성.
peerconnect_init();
SurfaceViewRenderer renderer = findViewById(R.id.remoteview1);
renderer.init(eglBaseContext, new RendererCommon.RendererEvents() {
// 첫 번째 프레임이 렌더링되면 콜백이 실행됩니다.
@Override
public void onFirstFrameRendered() {
Log.i("RendererEvents","onFirstFrameRendered");
}
// 렌더링된 프레임 해상도 또는 회전이 변경되면 콜백이 실행됩니다.
@Override
public void onFrameResolutionChanged(int i, int i1, int i2) {
Log.i("RendererEvents","onFrameResolutionChanged");
}
});
알림 = findViewById(R.id.button);
알림.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// MediaProjectionManager 클래스에서 제공하는 메서드 중 하나로, 안드로이드 장치의 화면 캡처 권한을 요청하는 데 사용
Intent intent = mediaProjectionManager.createScreenCaptureIntent();
// startActivityForResult()는 안드로이드에서 제공하는 메서드 중 하나로,
// 다른 액티비티를 시작하고 그 결과를 받아올 때 사용됩니다.
// 실행된 액티비티로부터 결과를 돌려받을 때 사용할 요청 코드입니다. : 실행된 액티비티로부터 결과를 돌려받을 때 사용할 요청 코드입니다.
startActivityForResult(intent, REQUEST_CODE_SCREEN_CAPTURE);
// 안드로이드 장치의 화면 캡처 권한을 요청하는 Activity가 화면에 보여짐.
// pause 발생.
}
});
중지 = findViewById(R.id.button2);
중지.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
videoCapturer.stopCapture();
videoCapturer.dispose();
getApplicationContext().stopService(new Intent(MainActivity.this, MyService.class));
} catch (InterruptedException e) {
e.printStackTrace();
Log.i("stopCapture err",e.toString());
}
}
});
}
// 안드로이드 운영 체제에서 앱의 다른 구성 요소 간 통신을 가능하게 하는 클래스입니다.
//ResultReceiver 객체는 백그라운드 스레드에서 오랜 시간 동안 수행되는 작업을 처리하고 UI 스레드로 결과를 전달해야 할 때 유용합니다.
// 이 클래스는 다른 프로세스에서 실행되는 컴포넌트로부터 결과 또는 메시지를 수신할 수 있습니다.
private ResultReceiver resultReceiver = new ResultReceiver(handler){ // (3)
@Override
// 서비스에서 전달된 결과를 처리합니다.
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
// SYNC_COMPLETED CODE = 1
if (resultCode == 1){ // (4)
// 카메라나 화면과 같은 미디어 소스에서 비디오 프레임을 캡처하기 위한 VideoSource 개체를 만드는 WebRTC(웹 실시간 통신) API의 메서드
// videoCapturer.isScreencast() : 특정 비디오 캡처러가 화면에서 비디오를 캡처하고 있는지 여부를 확인하는 데 사용
// videoCapturer가 화면에서 비디오를 캡처하는 경우 true이고(즉, 화면 캡처인 경우) 카메라 또는 다른 장치에서 비디오를 캡처하는 경우 false입니다.
videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast());
// 초기화가 메인에 있을때 문제가 없나?
// 초기화. surfaceTextureHelper
videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
// Parcelable msg = resultData.getParcelable("msg");
// videoCapturer 객체에서 비디오를 캡처하고 비디오 프레임을 WebRTC 스트림으로 보내기 시작합니다.
// 비디오 프레임의 너비, 높이 및 초당 프레임 수를 인수로 받습니다.
videoCapturer.startCapture(240, 320, 30);
// peerConnectionFactory 객체에서 비디오 트랙을 만듭니다.
// 트랙 식별자, 비디오 소스
VideoTrack stream = peerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
SurfaceViewRenderer renderer = findViewById(R.id.remoteview1);
stream.addSink(renderer);
}
}
};
// static Intent data;
@Override
// startActivityForResult() 메서드를 통해 실행한 액티비티로부터 결과를 받아오는 역할
// 요청 코드(REQUEST_CODE), 결과 코드(resultCode), 결과 데이터(data)
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// requestCode는 startActivityForResult() 메서드에서 지정한 요청 코드(REQUEST_CODE)와 비교하여 권한 요청에 대한 결과를 처리
// resultCode는 실행한 액티비티에서 설정한 결과 코드를 비교하여 권한이 부여되었는지 여부를 확인합니다.
// 결과가 RESULT_OK인 경우, RESULT_CANCELED인 경우
Log.i("캡쳐","성공");
Log.i("requestCode",Integer.toString(requestCode));
Log.i("resultCode",Integer.toString(resultCode));
// WEBRTC 에서 제공하는 안드로이드에서 화면 캡처를 수행하는 데 사용되는 클래스
// 매개변수 설명 (사용법)
// 반환값. : videoCapturer 카메라나 화면과 같은 미디어 소스에서 비디오를 캡처하기 위해 WebRTC(Web Real-Time Communication) API에 사용되는 클래스 또는 인터페이스입니다.
// *이 클래스는 안드로이드 5.0(API 레벨 21) 이상에서 사용할 수 있으며 (가상 기기에서 20레벨 못찾음)
//android.permission.RECORD_AUDIO 권한과 android.permission.WRITE_EXTERNAL_STORAGE 권한이 필요
videoCapturer = new ScreenCapturerAndroid(
// data : mediaProjectionPermissionResultData (권한 결과 데이터)
// android.media.projection.MediaProjection.Callback mediaProjectionCallback
data, new MediaProjection.Callback() {
@Override
// 화면 캡처 중지시 처리할 코드 작성
// 사용자 또는 시스템에 의해 미디어 프로젝션이 중지될 때 호출
// 이 메서드는 별도의 스레드에서 호출되므로 이 메서드 내에서 사용자 인터페이스를 업데이트하지 않아야 합니다.
public void onStop() {
Log.i("Callback ","onStop evnet");
}
});
// 인텐트 생성
Intent intent = new Intent(MainActivity.this, MyService.class);
// MyResultReceiver 객체를 생성하여 putExtra() 메소드를 사용하여 Intent의 extra로 전달합니다.
intent.putExtra("RECEIVER", resultReceiver); //(2)
// 스타트 포그라운드 서비스() 방식이 안드로이드 오레오에 도입돼 안드로이드 8.0 이상을 구동하는 기기에서만 이용할 수 있기 때문이다.
// 장치에서 이전 버전의 Android를 실행하는 경우 startService() 메서드가 대신 사용됩니다.
// Android Oreo 이상을 실행하는 경우 startService() 대신 startForgroundService() 메서드가 사용되어 포그라운드 서비스를 시작
// 현재 Android 장치에서 Android Oreo(버전 8.0) 이상(26이상) 사용.
// Build.VERSION_CODES.O는 Android Oreo(버전 8.0)의 API 레벨 26을 나타내는 상수입니다.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 안드로이드 운영 체제에서 포그라운드 서비스를 시작하는 메서드
getApplicationContext().startForegroundService(intent);
}
else{
getApplicationContext().startService(intent);
}
Toast.makeText(this, "서비스 시작"+intent.toString(), Toast.LENGTH_SHORT).show();
}
// 피어 연결 초기화
void peerconnect_init(){
// PeerConnectionFactory 초기화 및 static PeerConnectionFactory 객체 생성
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(getApplicationContext())
.setEnableInternalTracer(true)
.createInitializationOptions());
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
Log.i("options : ",options.toString());
peerConnectionFactory = PeerConnectionFactory.builder()
.setOptions(options)
.createPeerConnectionFactory();
Log.i("peerConnectionFactory", peerConnectionFactory.toString());
}
}
MyService.java 코드입니다.
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.util.Log;
import androidx.annotation.Nullable;
import org.webrtc.VideoTrack;
// MyService 라는 이름의 class 선언 {} 안에 필드, 메소드 선언
// Service 라는 이름의 클래스를 상속받은 class 를 선언.
// 안드로이드에서 백그라운드에서 실행되는 서비스를 제공할 수 있습니다.
public class MyService extends Service {
// 오버라이딩, 함수 재정의
@Override
// 서비스가 종료될 때 실행할 작업을 정의합니다.
public void onDestroy() {
// 부모의 onDestroy 함수 호출
super.onDestroy();
// MyService onDestroy 출력
Log.i("MyService","onDestroy");
// onDestroy 함수 끝.
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i("서비스 ","onBind");
return null;
}
public static final String CHANNEL_ID = "ForegroundServiceChannel";
// 오버라이딩, 함수 재정의
@Override
// 서비스가 생성될 때 실행할 작업을 정의합니다
public void onCreate() {
// 부모의 onDestroy 함수 호출
super.onCreate();
Log.i("서비스 ","onCreate");
createNotificationChannel();
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 첫 번째 인수는 채널 ID를 나타내며, 두 번째 인수는 채널 이름을 지정합니다. 세 번째 인수는 채널의 중요도를 지정합니다
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
assert manager != null;
//새로운 알림 채널을 등록
manager.createNotificationChannel(serviceChannel);
}
}
// 오버라이딩, 함수 재정의
@Override
// 서비스가 시작될 때 실행할 작업을 정의합니다.
// 메인 작업 처리?
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("서비스 ","onStartCommand");
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent,
PendingIntent.FLAG_IMMUTABLE);
// setContentTitle(), setContentText(), setSmallIcon() 메서드를 사용하여 알림의 제목, 내용 및 작은 아이콘을 설정합니다.
// setContentIntent() 메서드를 사용하여 알림을 탭했을 때 실행될 인텐트를 설정합니다.
Notification notification =
null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
notification = new Notification.Builder(getApplicationContext(), CHANNEL_ID)
.setContentTitle("제목1")
//getText(R.string.notification_title)
.setContentText("내용")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
// .setTicker("Ticker")
// .setAutoCancel(true)
.build();
startForeground(1, notification);
Log.i("서비스 ","노티 시작");
}
Log.i("노티 : ", notification.toString());
Bundle bundle = new Bundle();
//ResultReceiver를 가져온다.
//Parcelable를 상속받아서 구현하는 ResultReceiver
//ResultReceiver : 백그라운드 서비스에서 작업을 수행하고, 결과를 메인 스레드에 반환하는 데 사용
ResultReceiver receiver = intent.getParcelableExtra("RECEIVER"); // (1)
bundle.putString("msg","Succeed !");
// send() 메서드를 호출하여 결과를 반환하면 onReceiveResult() 메서드가 자동으로 호출
//결과 코드(resultCode)와 결과 데이터(bundle)를 인수로 받습니다.
receiver.send(1,bundle); // (2)
// stream.addSink(renderer);
// 부모 onStartCommand 호출, 반환.
return super.onStartCommand(intent, flags, startId);
// onStartCommand 함수의 끝.
}
// MyService 클래스 끝.
}
activity_main.xml 코드입니다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="내 화면 가져오기"
app:layout_constraintTop_toBottomOf="@+id/remoteview1"
tools:layout_editor_absoluteX="200dp" />
<org.webrtc.SurfaceViewRenderer
android:id="@+id/remoteview1"
android:layout_width="0dp"
android:layout_height="400dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="중지"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />
</androidx.constraintlayout.widget.ConstraintLayout>
'안드로이드 자바' 카테고리의 다른 글
[Java] awt 테트리스 모듈화 - Figure (0) | 2023.07.17 |
---|---|
[Android][Java] Joda-Time 활용해서 현재 시간 알아내기 (0) | 2023.07.08 |
[Android][JAVA] ListView로 구현하는 TodoList (0) | 2023.07.03 |
[Android][Java] 네이버 map api - 커스텀 마커와 마커 클릭 이벤트 다루기 (0) | 2023.07.01 |
[Android][JAVA] TextToSpeech를 사용해 텍스트(Text)를 음성으로 바꾸기 (0) | 2023.06.26 |