web-dev-qa-db-ja.com

recyclerviewでtextureviewを使用してビデオを再生する

VineやInstagramアプリなどのビデオを含むリストを実装しようとしています。ビデオを再生する場所は、リストアイテムが表示または完全に表示されるときに再生され、リストアイテムが非表示になるとビデオが一時停止します。テクスチャビューをメディアプレーヤーで使用してURLからビデオを再生し、recyclerviewのリストアイテムとして追加しました。以下は私のコードです。

VideosAdapterクラス:

public class VideosAdapter extends RecyclerView.Adapter<VideosAdapter.ViewHolder> {

Context context;
private ArrayList<String> urls;

public static class ViewHolder extends RecyclerView.ViewHolder {

    public LinearLayout layout;
    public TextView textView;

    public ViewHolder(View v) {
        super(v);
        layout = (LinearLayout) v.findViewById(R.id.linearLayout);
        textView = (TextView) v.findViewById(R.id.textView);
    }
}

public VideosAdapter(Context context, ArrayList<String> urls) {
    this.context = context;
    this.urls = urls;
}

// Create new views (invoked by the layout manager)
@Override
public VideosAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    // create a new view
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_main, parent, false);
    ViewHolder viewHolder = new ViewHolder(v);
    return viewHolder;
}

// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {

    String url = urls.get(position);
    holder.textView.setText(url);
    playVideo(holder, url);
}

@Override
public int getItemCount() {
    return urls.size();
}

private void playVideo(ViewHolder holder, String url)
{
    final CustomVideoPlayer vid = new CustomVideoPlayer(String.valueOf(url), context);
    holder.layout.addView(vid);
    holder.layout.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            vid.changePlayState();
        }
    });
}
}

CustomVideoPlayerクラス:

public class CustomVideoPlayer extends  TextureView implements TextureView.SurfaceTextureListener
{

Context context;
String url;
MediaPlayer mp;
Surface surface;
SurfaceTexture s;

public CustomVideoPlayer(Context context, AttributeSet attrs)
{
    super(context, attrs);
    this.context = context;
}

public CustomVideoPlayer(String ur, Context context)
{
    super(context);
    this.setSurfaceTextureListener(this);
    this.url = ur;
    this.context = context;

}

@Override
public void onSurfaceTextureAvailable(final SurfaceTexture surface, int arg1, int arg2) {

    this.s = surface;
    Log.d("url", this.url);
    startVideo(surface);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture arg0) {

    return true;
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture arg0, int arg1,int arg2) {
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture arg0) {
}

public void setVideo(String url)
{
    this.url = url;
}

public void startVideo(SurfaceTexture t)
{
    this.surface = new Surface(t);
    this.mp = new MediaPlayer();
    this.mp.setSurface(this.surface);
    try {
        Uri uri = Uri.parse(this.url);
        this.mp.setDataSource(url);
        this.mp.prepareAsync();

        this.mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            public void onPrepared(MediaPlayer mp) {

                mp.setLooping(true);
                mp.start();

            }
        });
    } catch (IllegalArgumentException e1) {
        e1.printStackTrace();
    } catch (SecurityException e1) {
        e1.printStackTrace();
    } catch (IllegalStateException e1) {
        e1.printStackTrace();
    } catch (IOException e1) {
        e1.printStackTrace();
    }
    try {

    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalStateException e) {
        e.printStackTrace();
    }
    try {

    } catch (IllegalStateException e) {
        e.printStackTrace();
    }


}

public void changePlayState()
{
    if(this.mp.isPlaying())
        this.mp.pause();
    else
        this.mp.start();
}
}

このコードを実行すると、複数の問題が発生します。

1)最初の2つのアイテム/ビデオがバッファリングされ、正常に再生されます。しかし、スクロールすると3番目のビデオはロードされず、最初のビデオもリストから削除されます。

2)スクロール時に、ビデオ/リストアイテムは、既にバッファリングされたアイテムのバッファリングを再開します。

3)高速スクロールリストで遅延が発生し、スタックしてクラッシュします。

添付されているのは、リストのスクロールとビデオの再生中に取得するlogcatの画像です。

enter image description here

誰でもこれをガイドできますか? Vineアプリのようなリストを作成する正しい方法は何ですか?

19
Nouman Bhatti

最初にURLからビデオをダウンロードしてからカスタムプレーヤーで再生することで、それを達成できました。他の誰かがそれを必要とした場合に備えて、私は次のようになりました:

1)再生する必要があるすべてのURLを取得する

2)ローカルストレージのURLから(キュー内の)ビデオのダウンロードを開始し、プリファレンスにフラグを保持します(ビデオが既にダウンロードされているかどうか)

3)ビデオ再生を処理するビデオプレーヤーコントローラーのオブジェクトを初期化するURLをアダプターに割り当てます

4)addOnScrollListenerを設定して、現在表示されている位置/ビデオを確認し、ビデオが既にダウンロードされているかどうかを確認し、そうであれば再生します。

完全なコードは次のとおりです。

MainActivity

public class MainActivity extends ActionBarActivity implements IVideoDownloadListener {

private static String TAG = "MainActivity";

private Context context;
private RecyclerView mRecyclerView;
private ProgressBar progressBar;
private VideosAdapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private ArrayList<Video> urls;
VideosDownloader videosDownloader;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    context = MainActivity.this;
    mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
    progressBar = (ProgressBar) findViewById(R.id.progressBar);
    urls = new ArrayList<Video>();
    mRecyclerView.setHasFixedSize(true);
    mLayoutManager = new LinearLayoutManager(this);
    mRecyclerView.setLayoutManager(mLayoutManager);
    mAdapter = new VideosAdapter(MainActivity.this, urls);
    mRecyclerView.setAdapter(mAdapter);

    videosDownloader = new VideosDownloader(context);
    videosDownloader.setOnVideoDownloadListener(this);

    if(Utils.hasConnection(context))
    {
        getVideoUrls();

        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);

                if (newState == RecyclerView.SCROLL_STATE_IDLE) {

                    LinearLayoutManager layoutManager = ((LinearLayoutManager) recyclerView.getLayoutManager());
                    int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
                    int findFirstCompletelyVisibleItemPosition = layoutManager.findFirstCompletelyVisibleItemPosition();

                    Video video;
                    if (urls != null && urls.size() > 0)
                    {
                        if (findFirstCompletelyVisibleItemPosition >= 0) {
                            video = urls.get(findFirstCompletelyVisibleItemPosition);
                            mAdapter.videoPlayerController.setcurrentPositionOfItemToPlay(findFirstCompletelyVisibleItemPosition);
                            mAdapter.videoPlayerController.handlePlayBack(video);
                        }
                        else
                        {
                            video = urls.get(firstVisiblePosition);
                            mAdapter.videoPlayerController.setcurrentPositionOfItemToPlay(firstVisiblePosition);
                            mAdapter.videoPlayerController.handlePlayBack(video);
                        }
                    }
                }
            }
        });
    }
    else
        Toast.makeText(context, "No internet available", Toast.LENGTH_LONG).show();
}

@Override
public void onVideoDownloaded(Video video) {
    mAdapter.videoPlayerController.handlePlayBack(video);
}

private void getVideoUrls()
{
    Video video1 = new Video("0", "1", "http://techslides.com/demos/sample-videos/small.mp4");
    urls.add(video1);
    Video video2 = new Video("1", "2", "http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4");
    urls.add(video2);
    Video video3 = new Video("2", "3", "http://sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4");
    urls.add(video3);
    Video video4 = new Video("3", "4", "http://dev.exiv2.org/attachments/341/video-2012-07-05-02-29-27.mp4");
    urls.add(video4);
    Video video5 = new Video("4", "5", "http://techslides.com/demos/sample-videos/small.mp4");
    urls.add(video5);
    Video video6 = new Video("5", "6", "http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4");
    urls.add(video6);
    Video video7 = new Video("6", "7", "http://sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4");
    urls.add(video7);

    mAdapter.notifyDataSetChanged();
    progressBar.setVisibility(View.GONE);
    videosDownloader.startVideosDownloading(urls);
 }
}

VideosAdapter

public class VideosAdapter extends RecyclerView.Adapter<VideosAdapter.ViewHolder> {

private static String TAG = "VideosAdapter";

Context context;
private ArrayList<Video> urls;
public VideoPlayerController videoPlayerController;

public static class ViewHolder extends RecyclerView.ViewHolder {

    public TextView textView;
    public ProgressBar progressBar;
    public RelativeLayout layout;

    public ViewHolder(View v) {
        super(v);
        layout = (RelativeLayout) v.findViewById(R.id.layout);
        textView = (TextView) v.findViewById(R.id.textView);
        progressBar = (ProgressBar) v.findViewById(R.id.progressBar);

    }
}

public VideosAdapter(Context context, final ArrayList<Video> urls) {

    this.context = context;
    this.urls = urls;
    videoPlayerController = new VideoPlayerController(context);
}

// Create new views (invoked by the layout manager)
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    // create a new view
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_main, parent, false);

    Configuration configuration = context.getResources().getConfiguration();
    int screenWidthDp = configuration.screenWidthDp; //The current width of the available screen space, in dp units, corresponding to screen width resource qualifier.
    int smallestScreenWidthDp = configuration.smallestScreenWidthDp; //The smallest screen size an application will see in normal operation, corresponding to smallest screen width resource qualifier.

    ViewHolder viewHolder = new ViewHolder(v);

    int screenWidthPixels = Utils.convertDpToPixel(screenWidthDp, context);
    RelativeLayout.LayoutParams rel_btn = new RelativeLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT, screenWidthPixels);
    viewHolder.layout.setLayoutParams(rel_btn);

    return viewHolder;
}

// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {

    Video video = urls.get(position);
    holder.textView.setText("Video " + video.getId());

    final VideoPlayer videoPlayer = new VideoPlayer(context);
    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
    videoPlayer.setLayoutParams(params);

    holder.layout.addView(videoPlayer);
    videoPlayerController.loadVideo(video, videoPlayer, holder.progressBar);
    videoPlayer.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            videoPlayer.changePlayState();
        }
    });
}

@Override
public void onViewRecycled(ViewHolder holder) {
    super.onViewRecycled(holder);
    Log.d(TAG, "onViewRecycledCalled");
    holder.layout.removeAllViews();

}

@Override
public int getItemCount() {
    return urls.size();
}

}

VideosDownloader

public class VideosDownloader {

private static String TAG = "VideosDownloader";

Context context;
FileCache fileCache;
IVideoDownloadListener iVideoDownloadListener;

public VideosDownloader(Context context) {
    this.context = context;
    fileCache = new FileCache(context);
}

/////////////////////////////////////////////////////////////////
// Start downloading all videos from given urls

public void startVideosDownloading(final ArrayList<Video> videosList)
{
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run()
        {
            for(int i=0; i<videosList.size(); i++)
            {
                final Video video = videosList.get(i);
                String id = video.getId();
                String url = video.getUrl();

                String isVideoDownloaded = Utils.readPreferences(context, video.getUrl(), "false");
                boolean isVideoAvailable = Boolean.valueOf(isVideoDownloaded);
                if(!isVideoAvailable)
                {
                    //Download video from url
                    String downloadedPath = downloadVideo(url);
                    //Log.i(TAG, "Vides downloaded at: " + downloadedPath);
                    Activity activity = (Activity) context;
                    activity.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Utils.savePreferences(context, video.getUrl(), "true");
                             iVideoDownloadListener.onVideoDownloaded(video);
                        }
                    });
                }

            }
        }
    });
    thread.start();
}

/////////////////////////////////////////////////////////////////

private String downloadVideo(String urlStr)
{
    URL url = null;
    File file = null;
    try
    {
        file = fileCache.getFile(urlStr);
        url = new URL(urlStr);
        long startTime = System.currentTimeMillis();
        URLConnection ucon = null;
        ucon = url.openConnection();
        InputStream is = ucon.getInputStream();
        BufferedInputStream inStream = new BufferedInputStream(is, 1024 * 5);
        FileOutputStream outStream = new FileOutputStream(file);
        byte[] buff = new byte[5 * 1024];

        //Read bytes (and store them) until there is nothing more to read(-1)
        int len;
        while ((len = inStream.read(buff)) != -1) {
            outStream.write(buff, 0, len);
        }

        //clean up
        outStream.flush();
        outStream.close();
        inStream.close();

    }
    catch (MalformedURLException e) {
        e.printStackTrace();
    }
    catch (IOException e) {
        e.printStackTrace();
    }
    return file.getAbsolutePath();
}


public void setOnVideoDownloadListener(IVideoDownloadListener iVideoDownloadListener) {
    this.iVideoDownloadListener = iVideoDownloadListener;
}
}

VideoPlayerController

public class VideoPlayerController {

private static String TAG = "VideoPlayerController";

Context context;
FileCache fileCache;
int currentPositionOfItemToPlay = 0;
Video currentPlayingVideo;

private Map<String, VideoPlayer> videos = Collections.synchronizedMap(new WeakHashMap<String, VideoPlayer>());
private Map<String, ProgressBar> videosSpinner = Collections.synchronizedMap(new WeakHashMap<String, ProgressBar>());

public VideoPlayerController(Context context) {

    this.context = context;
    fileCache = new FileCache(context);
}

public void loadVideo(Video video, VideoPlayer videoPlayer, ProgressBar progressBar) {

    //Add video to map
    videos.put(video.getIndexPosition(), videoPlayer);
    videosSpinner.put(video.getIndexPosition(), progressBar);

    handlePlayBack(video);
}

//This method would check two things
//First if video is downloaded or its local path exist
//Second if the videoplayer of this video is currently showing in the list or visible

public void handlePlayBack(Video video)
{
    //Check if video is available
    if(isVideoDownloaded(video))
    {

        // then check if it is currently at a visible or playable position in the listview
        if(isVideoVisible(video))
        {
            //IF yes then playvideo
            playVideo(video);
        }
    }
}

private void playVideo(final Video video)
{
    //Before playing it check if this video is already playing

    if(currentPlayingVideo != video)
    {
        //Start playing new url
        if(videos.containsKey(video.getIndexPosition()))
        {
            final VideoPlayer videoPlayer2 = videos.get(video.getIndexPosition());
            String localPath = fileCache.getFile(video.getUrl()).getAbsolutePath();
            if(!videoPlayer2.isLoaded)
            {
                videoPlayer2.loadVideo(localPath, video);
                videoPlayer2.setOnVideoPreparedListener(new IVideoPreparedListener() {
                    @Override
                    public void onVideoPrepared(Video mVideo) {

                        //Pause current playing video if any
                        if(video.getIndexPosition() == mVideo.getIndexPosition())
                        {
                            if(currentPlayingVideo!=null)
                            {
                                VideoPlayer videoPlayer1 = videos.get(currentPlayingVideo.getIndexPosition());
                                videoPlayer1.pausePlay();
                            }
                            videoPlayer2.mp.start();
                            currentPlayingVideo = mVideo;
                        }


                    }
                });
            }
            else
            {
                //Pause current playing video if any
                if(currentPlayingVideo!=null)
                {
                    VideoPlayer videoPlayer1 = videos.get(currentPlayingVideo.getIndexPosition());
                    videoPlayer1.pausePlay();
                }

                boolean isStarted = videoPlayer2.startPlay();
                {
                    //Log.i(TAG, "Started playing Video Index: " + video.getIndexPosition());
                    //Log.i(TAG, "Started playing Video: " + video.getUrl());
                }
                currentPlayingVideo = video;
            }
        }
    }
    else
    {
        //Log.i(TAG, "Already playing Video: " + video.getUrl());
    }

}

private boolean isVideoVisible(Video video) {

    //To check if the video is visible in the listview or it is currently at a playable position
    //we need the position of this video in listview and current scroll position of the listview
    int positionOfVideo = Integer.valueOf(video.getIndexPosition());

    if(currentPositionOfItemToPlay == positionOfVideo)
        return true;

    return false;
}

private boolean isVideoDownloaded(Video video) {

    String isVideoDownloaded = Utils.readPreferences(context, video.getUrl(), "false");
    boolean isVideoAvailable = Boolean.valueOf(isVideoDownloaded);
    if(isVideoAvailable)
    {
        //If video is downloaded then hide its progress
        hideProgressSpinner(video);
        return true;
    }

    showProgressSpinner(video);
    return false;
}

private void showProgressSpinner(Video video) {
    ProgressBar progressBar = videosSpinner.get(video.getIndexPosition());
    if(progressBar!=null)
        progressBar.setVisibility(View.VISIBLE);
}

private void hideProgressSpinner(Video video) {

    ProgressBar progressBar = videosSpinner.get(video.getIndexPosition());
    if(progressBar!=null && progressBar.isShown())
    {
        progressBar.setVisibility(View.GONE);
        Log.i(TAG, "ProgressSpinner Hided Index: " + video.getIndexPosition());
    }
}

public void setcurrentPositionOfItemToPlay(int mCurrentPositionOfItemToPlay) {
    currentPositionOfItemToPlay = mCurrentPositionOfItemToPlay;
}
}

VideoPlayer

public class VideoPlayer extends TextureView implements TextureView.SurfaceTextureListener {

private static String TAG = "VideoPlayer";

/**This flag determines that if current VideoPlayer object is first item of the list if it is first item of list*/
boolean isFirstListItem;

boolean isLoaded;
boolean isMpPrepared;

IVideoPreparedListener iVideoPreparedListener;

Video video;
String url;
MediaPlayer mp;
Surface surface;
SurfaceTexture s;

public VideoPlayer(Context context) {
    super(context);
}

public VideoPlayer(Context context, AttributeSet attrs)
{
    super(context, attrs);
}

public void loadVideo(String localPath, Video video) {

    this.url = localPath;
    this.video = video;
    isLoaded = true;

    if (this.isAvailable()) {
        prepareVideo(getSurfaceTexture());
    }

    setSurfaceTextureListener(this);
}

@Override
public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) {
    isMpPrepared = false;
    prepareVideo(surface);
}

@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

}

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {

    if(mp!=null)
    {
        mp.stop();
        mp.reset();
        mp.release();
        mp = null;
    }

    return false;
}

@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}

public void prepareVideo(SurfaceTexture t)
{

    this.surface = new Surface(t);
    mp = new MediaPlayer();
    mp.setSurface(this.surface);

    try {
        mp.setDataSource(url);
        mp.prepareAsync();

        mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            public void onPrepared(MediaPlayer mp) {
                isMpPrepared = true;
                mp.setLooping(true);
                iVideoPreparedListener.onVideoPrepared(video);
            }


        });
    } catch (IllegalArgumentException e1) {
        e1.printStackTrace();
    } catch (SecurityException e1) {
        e1.printStackTrace();
    } catch (IllegalStateException e1) {
        e1.printStackTrace();
    } catch (IOException e1) {
        e1.printStackTrace();
    }
    try {

    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalStateException e) {
        e.printStackTrace();
    }
    try {

    } catch (IllegalStateException e) {
        e.printStackTrace();
    }

}

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
}

@Override
protected void onVisibilityChanged(View changedView, int visibility) {
    super.onVisibilityChanged(changedView, visibility);
}

public boolean startPlay()
{
    if(mp!=null)
        if(!mp.isPlaying())
        {
            mp.start();
            return true;
        }

    return false;
}

public void pausePlay()
{
    if(mp!=null)
        mp.pause();
}

public void stopPlay()
{
    if(mp!=null)
        mp.stop();
}

public void changePlayState()
{
    if(mp!=null)
    {
        if(mp.isPlaying())
            mp.pause();
        else
            mp.start();
    }

}

public void setOnVideoPreparedListener(IVideoPreparedListener iVideoPreparedListener) {
    this.iVideoPreparedListener = iVideoPreparedListener;
}
}

IVideoDownloadListener

public interface IVideoDownloadListener {
public void onVideoDownloaded(Video video);
}

IVideoPreparedListener

public interface IVideoPreparedListener {

public void onVideoPrepared(Video video);
}
28
Nouman Bhatti

動画をバックエンドでダウンロードして動画のキャッシュをローカルに維持し、ローカルメモリから動画を1つずつ再生して、リストのスクロールをスムーズに保つ必要があります。

4
Sajad Khan

レイアウトファイル「view_main」自体にカスタムビデオビューを追加しないのはなぜですか。ビデオビューの可視性を確認し、ビューが表示されている場合にのみ再生します。

public static boolean isViewVisible(View subView, View parentView) {
    Rect scrollBounds = new Rect();
    parentView.getHitRect(scrollBounds);
    if (subView.getLocalVisibleRect(scrollBounds)) {
        return true;
    }
    return false;
}

可視性をチェックするためのコード。スクロール状態がアイドルのときに、スクロール状態変更リスナーでこれを呼び出します。

また、ビデオのダウンロードにはAsyncTaskを使用する必要がありますが、一度に1つのビデオのみをダウンロードしないと、メモリ不足エラーが発生する可能性があります。

4
codename_47