Android기능_CameraX_VideoView_ExoPlayer_StyledPlayerView

소정·2023년 3월 8일
0

Android_with_Java

목록 보기
26/33

[1] CameraX API

  1. CameraX 라이브러리 추가
    https://developer.android.com/codelabs/camerax-getting-started#1
  2. 그래들에 추가
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}"
}
  1. 멀티 동적 퍼미션 받기
  • ActivityResultLauncher<>의 제네릭 타입에 리스트는 못옴 배열만 올수 있다

  • 그래서 퍼미션 어레이로 받고 배열로 바꿔주는 작업함

  • api28 버전 이하는 외부저장소 쓴다는 퍼미션도 받아줘야해서

  1. 카메라 프리뷰 시작하는 작업
  • 카메라 기능 제공자를 부르려면 리스너부터 붙이고 스레드 안에서 불러야함 (시간이 걸리는 작업이라 비동기 처리)
  • 카메라 기능을 불러들이는 리스너를 불러들이는 것 - 비동기 방식으로 ProcessCameraProvider을 가져옴

① 프로바이더
② 프리뷰기능 - 프리뷰 작업을 하는 객체 빌더 생성
③ 화면에 연결

💡 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));
    }

  1. 이미지 캡쳐 버튼
    ① 이미지 캽쳐 객체 소환
  • 카메라 앱은 프로바이더가 해독하는 리졸버가 있음
  • 지금 상황은 내 앱에서 카메라 엑스 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();
            }
        });

    }
}



[2] 비디오뷰

현재 현업에서는 쓰지않음! 그냥 참고로 보기만하자

  • 비디오는 용량이 커서 자동 File로 저장됨 (28버전 이하는 동적 퍼미션 필요)
  • 비디오 로딩 시간이 길어서 바로 실행 안됨 그래서 로딩 완료 리스너로 처리
  • 미디어프레이어가 내장되어 있는게 비디오뷰이다
  • 실제 비디오 파일은 용량이 크기때문에 앱의 res 폴더에 직접 가지고 있지 않음
  • 웹 서버(인터넷 경로)에 동영상을 업로드하고 이를 불러와서 재생함 : 정적 인터넷 퍼미션 필요

1.비디오 기본 사용법

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();
    }
}



[3] ExoPlayer **

https://exoplayer.dev/hello-world.html

  • 비디오 재생 라이브러리
  • 자동으로 로딩완료까지 기다렸다가 재생함 리스너 필요 없음
  • 비디오뷰는 현업에서 안씀
  • ExoPlayer가 리소스 관리 잘해줌

1. 기본 사용방법

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>

  1. 동영상 하나만 재생 방법
  2. 동영상 2개 이상 등록 후 연속 재생 방법
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>



2. 풀스크린 - 새로운 액티비티로 가로모드

  • 메인화면과 풀스크린 화면 전환의 동영상 재생위치 주고 받기 위해
  • 스타트 액티비티 포 리저트로 만들어서 데이터 주고 받을 수 있도록 하든가
  • 메인 액티비티를 finish하고 넘긴 뒤 다시 돌아오면 다시 만드는 방법 쓰기
  1. xml에 PlayerView 만들기

  2. 매니패스트에 스크린 방향 정하기

  • sensorLandscape : 핸드폰을 어느 방향으로 돌려도 위를 찾아 줌
  1. 풀스크린으로 화면 넘겨줄 때 Intent에 재생할 동영상/재생위치 정보를 같이 넘겨준다
  2. 다른 화면으로 전환할 때 아래 깔리는 화면은 따로 동작하는 상황이 없도록 화면은 멈춰주거나 finish() 해버린다

main.xml

<?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>

main.java

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;
    }
}

activity_full_screen.xml

<?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>

FullScreenActivity.java

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;
    }
}

☝ 별도 테마 해주기

  1. 테마에서 별도로 쓸 테마 작성
  2. 매니패스트에서 적용

☝ 상태 줄 없애기

윈도우에서 윈도우매니저에서 찾아 없애기

getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);



3. StyledPlayerView

  • 개선된 플레이어뷰 모양


사용방법


<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();
 }
 
profile
보조기억장치

0개의 댓글