YouTube 播放器 API 抛出异常

YouTube Player API throwing exception

我查看了其他线程,但没有找到解决方案,除了最初检测到 API 21,即 Lollipop。同时,我在 Lollipop 和 post-Lollipop 版本中都面临这个问题。

我正在使用 YouTube 数据 API,在我的应用程序中显示特定频道的内容。我成功地从 API 获得响应并在 RecyclerView 中显示内容。

但是当我尝试在 YouTubeSupportFragment 中加载视频时,应用程序在调用 YouTubeSupportFragmentcueVideo() 时崩溃,出现以下异常。

注意:我使用的是最新版本 (1.2.2) 的 YouTube API。

这是 YouTube Player 抛出的异常:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.acme.youtubeplayer, PID: 16757
    java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.google.android.youtube.api.service.START }
        at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:2101)
        at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:2225)
        at android.app.ContextImpl.bindService(ContextImpl.java:2203)
        at android.content.ContextWrapper.bindService(ContextWrapper.java:560)
        at com.google.android.youtube.player.internal.r.e(Unknown Source)
        at com.google.android.youtube.player.YouTubePlayerView.a(Unknown Source)
        at com.google.android.youtube.player.YouTubePlayerSupportFragment.a(Unknown Source)
        at com.google.android.youtube.player.YouTubePlayerSupportFragment.onCreateView(Unknown Source)
        at android.support.v4.app.Fragment.performCreateView(Fragment.java:2192)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1299)
        at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1528)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1595)
        at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:758)
        at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2363)
        at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2149)
        at android.support.v4.app.FragmentManagerImpl.optimizeAndExecuteOps(FragmentManager.java:2103)
        at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2013)
        at android.support.v4.app.FragmentManagerImpl.run(FragmentManager.java:710)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:145)
        at android.app.ActivityThread.main(ActivityThread.java:7007)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)

这是我的 ListFragment.java,应用崩溃的地方:

public class ListFragment extends android.support.v4.app.Fragment {
    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;

    private static final String KEY_TRANSITION_EFFECT = "transition_effect";
    private static final int RECOVERY_REQUEST = 1;

    private int mCurrentTransitionEffect = JazzyHelper.HELIX;
    private JazzyRecyclerViewScrollListener jazzyScrollListener;
    YouTubePlayerView youtube_player;
    MyPlayerStateChangeListener playerStateChangeListener;
    MyPlaybackEventListener playbackEventListener;
    YouTubePlayer playerFragment;

    JSONObject jObjectAPI;
    JSONArray jArrayResponse;
    int listSize;
    JSONObject jObjectResponse, jObject;

    public static final String YOUTUBE_API = "https://www.googleapis.com/youtube/v3/search?key=" + Config.YOUTUBE_API_KEY + "&channelId=" + Config.YOUTUBE_CHANNEL_ID + "&part=snippet,id&order=date&maxResults=20";

    //Local Variables
    RecyclerView recyclerView;
    Button seekToButton;

    YouTubePlayerSupportFragment youTubePlayerFragment;
    String[] thumbnailVideo;
    String[] titleVideo;
    String[] descriptionVideo;
    String[] idVideo;
    int itemLayoutRes = R.layout.item;
    boolean isStaggered = false;

    private OnFragmentInteractionListener mListener;

    private ProgressDialog pDialog;

    public ListFragment() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment ListFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static ListFragment newInstance(String param1, String param2) {
        ListFragment fragment = new ListFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_list, container, false);
        // Inflate the layout for this fragment

        recyclerView = (RecyclerView) view.findViewById(R.id.rv_video_list);

        recyclerView.setLayoutManager(createLayoutManager(itemLayoutRes, isStaggered));
        recyclerView.setHasFixedSize(false);
        new GetList().execute();

        return view;
    }

    // TODO: Rename method, update argument and hook method into UI event
    public void onButtonPressed(Uri uri) {
        if (mListener != null) {
            mListener.onFragmentInteraction(uri);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    /**
     * This interface must be implemented by activities that contain this
     * fragment to allow an interaction in this fragment to be communicated
     * to the activity and potentially other fragments contained in that
     * activity.
     * <p>
     * See the Android Training lesson <a href=
     * "http://developer.android.com/training/basics/fragments/communicating.html"
     * >Communicating with Other Fragments</a> for more information.
     */
    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        void onFragmentInteraction(Uri uri);
    }

    private RecyclerView.LayoutManager createLayoutManager(int itemLayoutRes, boolean isStaggered) {
        if (itemLayoutRes == R.layout.item) {
            return new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
        } else {
            if (isStaggered) {
                return new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
            } else {
                return new GridLayoutManager(getActivity(), 1);
            }
        }
    }

    private void setupJazziness(int effect) {
        mCurrentTransitionEffect = effect;
        jazzyScrollListener.setTransitionEffect(mCurrentTransitionEffect);
    }

    private void showMessage(String message) {
        Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
    }

    public final class MyPlaybackEventListener implements YouTubePlayer.PlaybackEventListener {

        @Override
        public void onPlaying() {
            // Called when playback starts, either due to user action or call to play().
            showMessage("Playing");
        }

        @Override
        public void onPaused() {
            // Called when playback is paused, either due to user action or call to pause().
            showMessage("Paused");
        }

        @Override
        public void onStopped() {
            // Called when playback stops for a reason other than being paused.
            showMessage("Stopped");
        }

        @Override
        public void onBuffering(boolean b) {
            // Called when buffering starts or ends.
        }

        @Override
        public void onSeekTo(int i) {
            // Called when a jump in playback position occurs, either
            // due to user scrubbing or call to seekRelativeMillis() or seekToMillis()
        }
    }

    public final class MyPlayerStateChangeListener implements YouTubePlayer.PlayerStateChangeListener {

        @Override
        public void onLoading() {
            // Called when the youtube_player is loading a video
            // At this point, it's not ready to accept commands affecting playback such as play() or pause()
            playerFragment.loadVideo(idVideo.toString());
        }

        @Override
        public void onLoaded(String s) {
            // Called when a video is done loading.
            // Playback methods such as play(), pause() or seekToMillis(int) may be called after this callback.
            playerFragment.play();
        }

        @Override
        public void onAdStarted() {
            // Called when playback of an advertisement starts.
            playerFragment.pause();
        }

        @Override
        public void onVideoStarted() {
            // Called when playback of the video starts.
        }

        @Override
        public void onVideoEnded() {
            // Called when the video reaches its end.
            if (playerFragment.hasNext()) {
                playerFragment.next();
            }
        }

        @Override
        public void onError(YouTubePlayer.ErrorReason errorReason) {
            // Called when an error occurs.
            Toast.makeText(getActivity(), "Please check your Internet Connection", Toast.LENGTH_LONG).show();
        }
    }

    public static JSONObject getJSONObjectFromURL(String urlString) throws IOException, JSONException {

        HttpURLConnection urlConnection = null;

        URL url = new URL(urlString);

        urlConnection = (HttpURLConnection) url.openConnection();

        urlConnection.setRequestMethod("GET");
        urlConnection.setReadTimeout(10000 /* milliseconds */);
        urlConnection.setConnectTimeout(15000 /* milliseconds */);

        urlConnection.setDoOutput(true);

        urlConnection.connect();

        BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));

        char[] buffer = new char[1024];

        String jsonString;

        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line + "\n");
        }
        br.close();

        jsonString = sb.toString();

        System.out.println("JSON: " + jsonString);

        return new JSONObject(jsonString);
    }

    private class GetList extends AsyncTask<Void, Void, Void> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            // Showing progress dialog
            pDialog = new ProgressDialog(getActivity());
            pDialog.setMessage("Please wait...");
            pDialog.setCancelable(false);
            pDialog.show();

        }

        @Override
        protected Void doInBackground(Void... params) {

            try {
                jObjectAPI = getJSONObjectFromURL(YOUTUBE_API);
                Log.e("response", String.valueOf(jObjectAPI));
                jArrayResponse = jObjectAPI.getJSONArray("items");
                Log.e("array", String.valueOf(jArrayResponse));

                listSize = jArrayResponse.length();

                thumbnailVideo = new String[listSize];
                titleVideo = new String[listSize];
                descriptionVideo = new String[listSize];
                idVideo = new String[listSize];

                for (int i = 0; i < listSize; i++) {
                    jObjectResponse = jArrayResponse.getJSONObject(i);
                    thumbnailVideo[i] = jObjectResponse.getJSONObject("snippet").getJSONObject("thumbnails")
                            .getJSONObject("default")
                            .optString("url");
                    titleVideo[i] = jObjectResponse.getJSONObject("snippet").optString("title");

                    if (!(jObjectResponse.getJSONObject("snippet").optString("description").equals(""))
                            && !(jObjectResponse.getJSONObject("snippet").optString("description").equals(null))
                            && (jObjectResponse.getJSONObject("snippet").optString("description").length() > 0)) {
                        descriptionVideo[i] = jObjectResponse.getJSONObject("snippet").optString("description");
                    } else {
                        descriptionVideo[i] = "No Description Found";
                    }

                    idVideo[i] = jObjectResponse.getJSONObject("id").optString("videoId");
                }
            } catch (JSONException e) {
                e.printStackTrace();
                Log.e("JSON Exception", e.toString());
            } catch (IOException e) {
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);
            // Dismiss the progress dialog
            if (pDialog.isShowing())
                pDialog.dismiss();
            /**
             * Updating parsed JSON data into ListView
             **/

            recyclerView.setAdapter(new VideoListAdapter(thumbnailVideo, titleVideo, descriptionVideo, idVideo, itemLayoutRes, getActivity()));
            jazzyScrollListener = new JazzyRecyclerViewScrollListener();
            recyclerView.setOnScrollListener(jazzyScrollListener);

            setupJazziness(R.anim.slide_left_in);
            playerStateChangeListener = new MyPlayerStateChangeListener();
            playbackEventListener = new MyPlaybackEventListener();

            youTubePlayerFragment = new YouTubePlayerSupportFragment();
            youTubePlayerFragment.initialize(Config.YOUTUBE_API_KEY, new YouTubePlayer.OnInitializedListener() {

                @Override
                public void onInitializationSuccess(YouTubePlayer.Provider provider, YouTubePlayer player, boolean wasRestored) {
                    playerFragment = player;

                    player.setPlayerStateChangeListener(playerStateChangeListener);
                    player.setPlaybackEventListener(playbackEventListener);

                    if (!wasRestored) {
                        player.cueVideos(Arrays.asList(idVideo));
                    }
                }

                @Override
                public void onInitializationFailure(YouTubePlayer.Provider arg0, YouTubeInitializationResult errorReason) {
                    // TODO Auto-generated method stub
                    if (errorReason.isUserRecoverableError()) {
                        errorReason.getErrorDialog(getActivity(), RECOVERY_REQUEST).show();
                    } else {
                        String error = String.format(getString(R.string.player_error), errorReason.toString());
                        Toast.makeText(getActivity(), error, Toast.LENGTH_LONG).show();
                    }
                }
            });

            android.support.v4.app.FragmentManager fmPlayer = getFragmentManager();
            FragmentTransaction transaction = fmPlayer.beginTransaction();
            transaction.replace(R.id.youtube_player_view, youTubePlayerFragment);
            transaction.commit();
        }
    }
}

这是我的 适配器 class:

public class VideoListAdapter extends Adapter<VideoListAdapter.VideoListViewHolder> {

    private List<String> thumbnail;
    private List<String> title;
    private List<String> desc;
    private List<String> id;
    private int itemLayoutRes;
    private static Activity mContext;

    public VideoListAdapter(String[] videoThumbnail, String[] videoTitle, String[] videoDesc, String[] videoId, int itemLayoutRes, Activity context) {
        this.thumbnail = Arrays.asList(videoThumbnail);
        this.title = Arrays.asList(videoTitle);
        this.desc = Arrays.asList(videoDesc);
        this.id = Arrays.asList(videoId);
        this.itemLayoutRes = itemLayoutRes;
        this.mContext = context;
    }

    @Override
    public VideoListAdapter.VideoListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View view;
        view = inflater.inflate(itemLayoutRes, parent, false);
        return new VideoListViewHolder(view);
    }

    @Override
    public void onBindViewHolder(final VideoListViewHolder holder, final int position) {

        Picasso.with(mContext).load(thumbnail.get(position)).into(new Target() {
            @Override
            public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
                // loaded bitmap is here (bitmap)
                holder.thumbnailVideo.setImageBitmap(bitmap);
            }

            @Override
            public void onBitmapFailed(Drawable errorDrawable) {
                Toast.makeText(mContext, "Image load failed", Toast.LENGTH_LONG).show();
            }

            @Override
            public void onPrepareLoad(Drawable placeHolderDrawable) {

            }
        });

        Log.e("thumbnail Adapter", String.valueOf(thumbnail.get(position)));
        holder.titleVideo.setText(title.get(position));
        holder.descVideo.setText(desc.get(position));

        holder.idVideo = id.get(position);
    }

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

    @Override
    public int getItemViewType(int position) {
        //   return isStaggered ? position % 2 : 0;
        return position;
    }

    public static class VideoListViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        final TextView titleVideo, descVideo;
        final YouTubeThumbnailView thumbnailVideo;

        String idVideo;

        public VideoListViewHolder(View view) {
            super(view);
            thumbnailVideo = (YouTubeThumbnailView) view.findViewById(R.id.thumbnail_video);
            titleVideo = (TextView) view.findViewById(R.id.title_video);
            descVideo = (TextView) view.findViewById(R.id.desc_video);
        }

        @Override
        public void onClick(View v) {
            Intent intent = new Intent(mContext, YouTubeBaseActivity.class);
            //Intent intent = YouTubeStandalonePlayer.createVideoIntent((Activity) v.getContext(), Config.YOUTUBE_API_KEY, idVideo);
            v.getContext().startActivity(intent);
        }
    }
}

终于解决了这个问题。我将 YouTubeSupportFragment 替换为 YouTubeBaseActivity 并使用 YouTubePlayerView 播放视频。

换句话说,我使用 Activity 扩展 YouTubeBaseActivity 来播放视频,而不是 Fragment

希望它能帮助解决问题的人。