dependencies {
def camerax_version = "1.1.0-beta01"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-video:${camerax_version}"
implementation "androidx.camera:camera-view:${camerax_version}"
implementation "androidx.camera:camera-extensions:${camerax_version}"
}
ActivityResultLauncher<>의 제네릭 타입에 리스트는 못옴 배열만 올수 있다
그래서 퍼미션 어레이로 받고 배열로 바꿔주는 작업함
api28 버전 이하는 외부저장소 쓴다는 퍼미션도 받아줘야해서
① 프로바이더
② 프리뷰기능 - 프리뷰 작업을 하는 객체 빌더 생성
③ 화면에 연결
💡 Surface 뷰 (고속버퍼뷰)
- 뷰들 중 빠르게 그려주는 친구
- 일반적인 뷰들은 화면에 그려내는데 오래걸림 그래서 탄생함
- 이중 버퍼뷰 형태
스크린에 직접 그리지 않고 메모리 상 화면(Surface)에 그림을 그린 뒤 화면에 통채로 준다
(-> 버퍼를 하나씩 배송하면 느리니까 한번에 묶어 보내는 느낌)- PreviewView는 서페이스를 상속받아 만든 뷰임
void startCamera(){
//카메라 기능 제공자를 부르려면,,,리스너부터 붙이고 스레드 안에서 불러야함
//카메라 기능을 불러들이는 리스너를 불러들이는 것 - 비동기 방식으로 ProcessCameraProvider을 가져옴
ListenableFuture<ProcessCameraProvider> listenableFuture= ProcessCameraProvider.getInstance(this);
listenableFuture.addListener(new Runnable() { //시간이 걸리는 작업이라 비동기 처리
@Override
public void run() {
try {
//1. 프로바이더
ProcessCameraProvider cameraProvider= listenableFuture.get(); //카메라 기능 제공자
//2. 프리뷰 작업을 하는 객체 빌더 생성
Preview.Builder builder= new Preview.Builder();
Preview preview= builder.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
// Surface : 뷰들 중 빠르게 그려주는 친구
//일반적인 뷰들은 화면에 그려내는데 오래걸림 그래서 탄생함
//이중 버퍼뷰 - 스크린에 직접 그리지 않고 메모리 상 화면(Surface)에 그림을 그리고
//그 뒤 그림을 통채로 줌
//여러개의 버퍼를 하나씩 배송하면 느리니까 한번에 묶어 보내는 느낌
//고속버퍼뷰
//PreviewView는 서페이스를 상속받아 만든 뷰임
CameraSelector cameraSelector= CameraSelector.DEFAULT_BACK_CAMERA;
//이미지 캡쳐 버튼 메소드 의 2. 이미지 캡쳐 객체 빌더를 통해 생성
imageCapture= new ImageCapture.Builder().build();
//3. bindToLifecycle(생명주기 연결 할 액티비티, 앞카메라 뒤카메라?, 아규먼트,,) : 아규먼트? 여거개 뒤에 계속 붙을 수 있단 뜻
cameraProvider.bindToLifecycle(MainActivity.this, cameraSelector, preview, imageCapture); //카메라 라이프 사이클 알아서 해주는 애
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, ContextCompat.getMainExecutor(this));
}
- 카메라 앱은 프로바이더가 해독하는 리졸버가 있음
- 지금 상황은 내 앱에서 카메라 엑스 DB를 오픈해서 탐색 해야됨
- 카메라 엑스는 이미 본인의 디비를 제공하기 위한 프로바이더가 있어서
- 내 앱에선 카메라 엑스 디비의 프로바이더가 준 것을 해독하기 위한 리졸버(운영체제 능력) 필요하다
콘텐츠 벨류
디비안에 뭔가 집어넣으려면 콘텐츠 벨류라는 박스에 넣고 디비에 한번에 밀어넣는다
디비에 밀어 넣을 땐 디비 이름과 맟줘서 넣어줘야함 (디비 컬럼명 , 넣을 값)
② 프로바이더 만드는 listenableFuture 리스너 안에서 이미지 캡쳐 객체 빌더를 통해 생성 후 bindToLifecycle 매개변수로 포함
③ 저장될 파일명 정하기 (보통 날짜)
④ 카메라 엑스의 미디어 DB에 저장할 한 줄(record : 하나의 파일 정보) 객체 만들기
⑤ 이미지 캡쳐에게 저장옵션(4번에 한거)으로 생성하기위해 옵션 객체 생성 (운영체제의 리졸버, 카메라 엑스의 Uri, 4번 values)
⑥ 이미지 캡처에게 촬영을 요청 - 위 옵션에 설정한 위치
⑦ 촬영이 잘 됏으면 이미지 img_uri
private void clickBtn() {
//1. 이미지 캽쳐 객체 소환
if(imageCapture == null) return;
//카메라 앱은 프로바이더가 해독하는 리졸버가 있음
//지금 상황은 내 앱에서 카메라 엑스 DB를 오픈해서 탐색 해야됨
//카메라 엑스는 이미 본인의 디비를 제공하기 위한 프로바이더가 있어서
//카메라 엑스 디비의 프로바이더가 준 것을 해독하기 위한 리졸버(운영체제 능력) 필요
//콘텐츠 벨류 => 디비안에 뭔가 집어넣으려면 콘텐츠 벨류에 넣고 디비에 밀어넣는다
//디비에 밀어 넣을 땐 디비 이름과 맟줘서 넣어줘야함 (디비 컬럼명 , 넣을 값)
//2.listenableFuture 리스너 안에서 이미지 캡쳐 객체 빌더를 통해 생성 후
// bindToLifecycle 매개변수로 포함
//3. 저장될 파일명
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String fileName = sdf.format(System.currentTimeMillis());
//4. 카메라 엑스의 미디어 DB에 저장할 한 줄(record : 하나의 파일 정보) 객체 만들기
ContentValues values = new ContentValues(); //한줄짜리 슬롯 만듦
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); //파일이름
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg"); //jpeg 사진 압축방식 파입
if(Build.VERSION.SDK_INT > 28) values.put(MediaStore.MediaColumns.RELATIVE_PATH,"Pictures/CameraX-Image"); //경로
//5. 이미지 캡쳐에게 저장옵션(4번에 한거)으로 생성하기위해 옵션 객체 생성 (운영체제의 리졸버, 카메라 엑스의 Uri, 4번 values)
ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(getContentResolver(),MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values).build();
//6. 이미지 캡처에게 촬영을 요청 - 위 옵션에 설정한 위치
imageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
Toast.makeText(MainActivity.this, "찰칵", Toast.LENGTH_SHORT).show();
//7. 촬영이 잘 됏으면 이미지 img_uri
img_uri.setText(outputFileResults.getSavedUri().toString());
Glide.with(MainActivity.this).load(outputFileResults.getSavedUri()).into(civ);
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
Toast.makeText(MainActivity.this, "error : "+ exception.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<androidx.camera.view.PreviewView
android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_marginBottom="80dp"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
app:backgroundTint="@color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/img_uri"
android:padding="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="40dp"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/civ"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginRight="24dp"
android:layout_marginBottom="80dp"
app:civ_border_color="@color/white"
app:civ_border_width="2dp"
android:layout_width="80dp"
android:layout_height="80dp"/>
</RelativeLayout>
package com.bsj0420.ex74camerax;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.camera.view.video.OutputFileOptions;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.google.common.util.concurrent.ListenableFuture;
import java.security.Permissions;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import de.hdodenhof.circleimageview.CircleImageView;
public class MainActivity extends AppCompatActivity {
//카메라X 라이브러리 추가
PreviewView previewView;
TextView img_uri;
CircleImageView civ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
previewView = findViewById(R.id.preview_view);
img_uri = findViewById(R.id.img_uri);
civ = findViewById(R.id.civ);
findViewById(R.id.fab).setOnClickListener(v->clickBtn());
//상태표시줄 영역까지 액티비티 확장
getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
//동적 퍼미션 처리
ArrayList<String> permissions = new ArrayList<>();
permissions.add(Manifest.permission.CAMERA);
permissions.add(Manifest.permission.RECORD_AUDIO);
if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
int checkResult = checkSelfPermission(permissions.get(0));
if (checkResult== PackageManager.PERMISSION_DENIED) {
//퍼미션들을 요청하는 대행사 이용
//리스트를 배열로 바꾸기
String[] arr = new String[permissions.size()];
permissions.toArray(arr); //리스트를 배열로 바꾸는 메소드
resultLauncher.launch(arr);
}
}
//퍼미션들을 요청하고 결과를 받아주는 계약을 체결하는 대행사 등록
// ActivityResultLauncher<> 는 제네릭에 배열만 올수 있다
ActivityResultLauncher<String[]> resultLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(),
new ActivityResultCallback<Map<String, Boolean>>() {
@Override
public void onActivityResult(Map<String, Boolean> result) {
//result 는 Map 방식이기 때문에 무조건 result.get("키이름")
//boolean a = result.get(Manifest.permission.CAMERA);
//Map 컬렉션은 foreach문 처리 불가능
//그래서 우선 키값들만 빼오기
Set<String> keys =result.keySet();
for(String key : keys) {
boolean value = result.get(key);
if(value) Toast.makeText(MainActivity.this, key + "을 허용함", Toast.LENGTH_SHORT).show();
else Toast.makeText(MainActivity.this, key + "불허함", Toast.LENGTH_SHORT).show();
}
}
});
//카메라 프리뷰 시작하는 작업 메소드
void startCamera(){
//카메라 기능 제공자를 부르려면,,,리스너부터 붙이고 스레드 안에서 불러야함
//카메라 기능을 불러들이는 리스너를 불러들이는 것 - 비동기 방식으로 ProcessCameraProvider을 가져옴
ListenableFuture<ProcessCameraProvider> listenableFuture= ProcessCameraProvider.getInstance(this);
listenableFuture.addListener(new Runnable() { //시간이 걸리는 작업이라 비동기 처리
@Override
public void run() {
try {
//1. 프로바이더
ProcessCameraProvider cameraProvider= listenableFuture.get(); //카메라 기능 제공자
//2. 프리뷰 작업을 하는 객체 빌더 생성
Preview.Builder builder= new Preview.Builder();
Preview preview= builder.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
// Surface : 뷰들 중 빠르게 그려주는 친구
//일반적인 뷰들은 화면에 그려내는데 오래걸림 그래서 탄생함
//이중 버퍼뷰 - 스크린에 직접 그리지 않고 메모리 상 화면(Surface)에 그림을 그리고
//그 뒤 그림을 통채로 줌
//여러개의 버퍼를 하나씩 배송하면 느리니까 한번에 묶어 보내는 느낌
//고속버퍼뷰
//PreviewView는 서페이스를 상속받아 만든 뷰임
CameraSelector cameraSelector= CameraSelector.DEFAULT_BACK_CAMERA;
//이미지 캡쳐 버튼 메소드 의 2. 이미지 캡쳐 객체 빌더를 통해 생성
imageCapture= new ImageCapture.Builder().build();
//3. bindToLifecycle(생명주기 연결 할 액티비티, 앞카메라 뒤카메라?, 아규먼트,,) : 아규먼트? 여거개 뒤에 계속 붙을 수 있단 뜻
cameraProvider.bindToLifecycle(MainActivity.this, cameraSelector, preview, imageCapture); //카메라 라이프 사이클 알아서 해주는 애
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, ContextCompat.getMainExecutor(this));
}
//카메라 시작은 화면에 뷰가 완전히 그려진 뒤 실행하고 싶어서 onResume() 에 넣음
//액티비티가 화면에 완전히 보여질 때 자동으로 발동하는 콜백 메소드
@Override
protected void onResume() {
super.onResume();
startCamera();
}
//1-1이미지 캡쳐 참조변수
ImageCapture imageCapture = null;
//이미지 캡쳐 버튼
private void clickBtn() {
//1. 이미지 캽쳐 객체 소환
if(imageCapture == null) return;
//카메라 앱은 프로바이더가 해독하는 리졸버가 있음
//지금 상황은 내 앱에서 카메라 엑스 DB를 오픈해서 탐색 해야됨
//카메라 엑스는 이미 본인의 디비를 제공하기 위한 프로바이더가 있어서
//카메라 엑스 디비의 프로바이더가 준 것을 해독하기 위한 리졸버(운영체제 능력) 필요
//콘텐츠 벨류 => 디비안에 뭔가 집어넣으려면 콘텐츠 벨류에 넣고 디비에 밀어넣는다
//디비에 밀어 넣을 땐 디비 이름과 맟줘서 넣어줘야함 (디비 컬럼명 , 넣을 값)
//2.listenableFuture 리스너 안에서 이미지 캡쳐 객체 빌더를 통해 생성 후
// bindToLifecycle 매개변수로 포함
//3. 저장될 파일명
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String fileName = sdf.format(System.currentTimeMillis());
//4. 카메라 엑스의 미디어 DB에 저장할 한 줄(record : 하나의 파일 정보) 객체 만들기
ContentValues values = new ContentValues(); //한줄짜리 슬롯 만듦
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); //파일이름
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg"); //jpeg 사진 압축방식 파입
if(Build.VERSION.SDK_INT > 28) values.put(MediaStore.MediaColumns.RELATIVE_PATH,"Pictures/CameraX-Image"); //경로
//5. 이미지 캡쳐에게 저장옵션(4번에 한거)으로 생성하기위해 옵션 객체 생성 (운영체제의 리졸버, 카메라 엑스의 Uri, 4번 values)
ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(getContentResolver(),MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values).build();
//6. 이미지 캡처에게 촬영을 요청 - 위 옵션에 설정한 위치
imageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
Toast.makeText(MainActivity.this, "찰칵", Toast.LENGTH_SHORT).show();
//7. 촬영이 잘 됏으면 이미지 img_uri
img_uri.setText(outputFileResults.getSavedUri().toString());
Glide.with(MainActivity.this).load(outputFileResults.getSavedUri()).into(civ);
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
Toast.makeText(MainActivity.this, "error : "+ exception.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}
현재 현업에서는 쓰지않음! 그냥 참고로 보기만하자
package com.bsj0420.ex73cameraappvidio;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.TextView;
import android.widget.VideoView;
public class MainActivity extends AppCompatActivity {
TextView tv;
VideoView vv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv= findViewById(R.id.tv);
vv= findViewById(R.id.vv);
findViewById(R.id.btn).setOnClickListener(v->clickBtn());
}
void clickBtn(){
//비디오는 용량이 커서 자동 파일로 저장됨. 28버전 이하는 동적퍼미션 필요
Intent intent= new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
resultLauncher.launch(intent);
}
ActivityResultLauncher<Intent> resultLauncher= registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if(result.getResultCode() == RESULT_CANCELED ) return;
Uri uri= result.getData().getData();
tv.setText(uri.toString());
vv.setVideoURI(uri);
//비디오가 로딩하는 시간이 소요됨. 그래서 로딩완료 리스너로 처리함
vv.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
vv.start();
}
});
});
}
다른화면을 갔다올 시 동영상 일시정지 시켜보기
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:padding="16dp"
tools:context=".MainActivity">
<VideoView
android:id="@+id/vv"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
package com.bsj0420.ex75videoview;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.widget.MediaController;
import android.widget.VideoView;
public class MainActivity extends AppCompatActivity {
VideoView vv;
//실제 비디오 파일은 용량이 크기때문에 앱의 res 폴더에 직접 가지고 있지 않음
//웹 서버(인터넷 경로)에 동영상을 업로드하고 이를 불러와서 재생함
//정적 인터넷 퍼미션 필요
String videoUrl = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
vv = findViewById(R.id.vv);
//비디오뷰에 재생, 일시정지 들을 할 수 있는 '컨트롤바'를 붙이기
vv.setMediaController(new MediaController(this));
//미디어프레이어가 내장되어 있는게 비디오뷰이다
vv.setVideoURI(Uri.parse(videoUrl));
//vv.start();
//동영상은 로딩하는 시간이 걸리기에 바로 스타트 불가하다
//로딩이 완료 됐을 때 재생하도록
vv.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
vv.start();
}
});
findViewById(R.id.btn).setOnClickListener(view -> {
startActivity(new Intent(this, SecondActivity.class));
});
}
//액티비티가 화면에서 안보이기 시작할 떄 자동으로 발동하는 콜백 메소드
@Override
protected void onPause() {
super.onPause();
//비디오 일시정지
if (vv != null && vv.isPlaying()) vv.pause();
}
//화면에 다시 보여질때
@Override
protected void onResume() {
super.onResume();
if(vv != null && !vv.isPlaying()) vv.start();
}
}
https://exoplayer.dev/hello-world.html
1. exoplayer 라이브러리 디펜던시
2. xml 에 PlayerView 프레임 만들어주기
resize_mode : 화면 비율 설정
3. main.java에서 비디오 플레이 url 연동
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="300dp"
app:resize_mode="fixed_width"/>
</RelativeLayout>
package com.bsj0420.ex76exoplayer;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.ui.StyledPlayerView;
public class MainActivity extends AppCompatActivity {
//비디오 재생 라이브러리 - 유튜브의 재생기준
PlayerView playerView;
ExoPlayer exoPlayer;
//새로운 플레이어 화면
StyledPlayerView styled_view;
ExoPlayer exoPlayer2;
//동영상 주소
Uri videoUri = Uri.parse("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
playerView = findViewById(R.id.player_view);
exoPlayer = new ExoPlayer.Builder(this).build();
playerView.setPlayer(exoPlayer);
//1. 영상 하나 플레이) 영상을 CD로 굽듯이
MediaItem mediaItem = MediaItem.fromUri(videoUri);
exoPlayer.setMediaItem(mediaItem);
exoPlayer.prepare();
exoPlayer.play(); //자동으로 로딩완료까지 기다렸다가 재생함
//2. 플레이리스트 처럼 여러개의 영상 등록 가능 (한 프레임에 여러개 동영상 연속 재생)
Uri furi = Uri.parse("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4");
Uri suri = Uri.parse("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4");
MediaItem item1 = MediaItem.fromUri(furi);
MediaItem item2 = MediaItem.fromUri(suri);
exoPlayer.addMediaItem(1,item1);
exoPlayer.addMediaItem(0,item2);
exoPlayer.prepare();
exoPlayer.play();
exoPlayer.setRepeatMode(ExoPlayer.REPEAT_MODE_ALL); //영상이 끝나면 설정할 것 - REPEAT_MODE_ALL : 전체 재생 반복
}
}
재생버튼/일시정지 버튼 등의 디자인을 내 맘대로 꾸미려면 정해진 파일의 이름[exo_player_control_view.xml]을 써야한다 그냥 정해진 xml 파일만 만들면 알아서 디자인이 변경된다
<br/
exo_player_control_view.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--레이아웃 이름과 id도 정해진 아이디 써야됨-->
<ImageButton
android:id="@+id/exo_play"
style="@style/ExoMediaButton.Play"
android:background="#66000000"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"/>
<ImageButton
android:id="@+id/exo_pause"
style="@style/ExoMediaButton.Pause"
android:background="#66000000"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"/>
</RelativeLayout>
xml에 PlayerView 만들기
매니패스트에 스크린 방향 정하기
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="300dp"
app:resize_mode="fixed_width"/>
<!-- 전체 화면보기 -->
<Button
android:id="@+id/full_btn"
android:layout_below="@+id/player_view"
android:text="전체화면"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
package com.bsj0420.ex76exoplayer;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.ui.StyledPlayerView;
public class MainActivity extends AppCompatActivity {
//비디오 재생 라이브러리 - 유튜브의 재생기준
PlayerView playerView;
ExoPlayer exoPlayer;
//새로운 플레이어 화면
StyledPlayerView styled_view;
ExoPlayer exoPlayer2;
//동영상 주소
Uri videoUri = Uri.parse("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
playerView = findViewById(R.id.player_view);
exoPlayer = new ExoPlayer.Builder(this).build();
playerView.setPlayer(exoPlayer);
//영상 하나 플레이) 영상을 CD로 굽듯이
MediaItem mediaItem = MediaItem.fromUri(videoUri);
exoPlayer.setMediaItem(mediaItem);
exoPlayer.prepare();
exoPlayer.play(); //자동으로 로딩완료까지 기다렸다가 재생함
exoPlayer.setRepeatMode(ExoPlayer.REPEAT_MODE_ALL); //영상이 끝나면 설정할 것 - REPEAT_MODE_ALL : 정체 재생 반복
//동영상 전체 화면
findViewById(R.id.full_btn).setOnClickListener(v -> clickBtn());
}
private void clickBtn() {
//별도의 전체화면용 액티비티 만들어 실행
Intent intent = new Intent(this, FullScreenActivity.class);
//현재 재생중인 uri 데이터 전달
intent.setData(videoUri);
//현재까지 재생된 위치정보 추가 전달
long pos = exoPlayer.getCurrentPosition();
intent.putExtra("crrPos",pos);
startActivity(intent);
}
//화면이 안보이기 시작 할 때 동영상 일시 정지
@Override
protected void onPause() {
super.onPause();
exoPlayer.pause();
}
//액티비티가 완전히 종료될 때 플레이어를 완전히 제거하는게 좋다
@Override
protected void onDestroy() {
super.onDestroy();
exoPlayer.stop();
exoPlayer.release(); //손을 놓다 -동영상은 Gpu 램 영역이라 찌꺼기 남을 수 있어서 해줘야함(메모리 누수 방지)
exoPlayer = null;
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".FullScreenActivity">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:resize_mode="fixed_width"
/>
</RelativeLayout>
package com.bsj0420.ex76exoplayer;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.WindowManager;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.ui.PlayerView;
public class FullScreenActivity extends AppCompatActivity {
PlayerView playerView;
ExoPlayer exoPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_full_screen);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
playerView = findViewById(R.id.player_view);
exoPlayer = new ExoPlayer.Builder(this).build();
playerView.setPlayer(exoPlayer);
//플레이 시킬 동영상의 Uri (인텐트를 통해 전달된 데이터)
Intent intent = getIntent();
Uri uri = intent.getData();
//재생 위치 얻어오기
long pos = intent.getLongExtra("crrPos",0);
MediaItem mediaItem = MediaItem.fromUri(uri);
exoPlayer.setMediaItem(mediaItem);
exoPlayer.prepare();
//재생위치로 seekbar 옮긴 뒤 play
exoPlayer.seekTo(pos);
exoPlayer.play();
}
@Override
protected void onPause() {
super.onPause();
exoPlayer.pause();
}
@Override
protected void onDestroy() {
super.onDestroy();
exoPlayer.stop();
exoPlayer.release(); //메모리 누수 방지
exoPlayer = null;
}
}
윈도우에서 윈도우매니저에서 찾아 없애기
getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
<com.google.android.exoplayer2.ui.StyledPlayerView
android:id="@+id/styled_view"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="300dp"/>
//새로운 플레이어 화면 참조변수
StyledPlayerView styled_view;
ExoPlayer exoPlayer2;
protected void onCreate(Bundle savedInstanceState) {
//개선된 컨트롤바 모양을 가진 스타일드 플레이어 뷰
styled_view = findViewById(R.id.styled_view);
exoPlayer2 = new ExoPlayer.Builder(this).build();
styled_view.setPlayer(exoPlayer2);
Uri furi = Uri.parse("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4");
MediaItem item = MediaItem.fromUri(furi);
exoPlayer2.setMediaItem(item);
exoPlayer2.prepare();
}