A基礎功能php
-html
B高級功能android
-git
C拓展功能github
-segmentfault
D待添加功能緩存
1.2.1目前僅僅查了下GitHub上項目網絡
ijkplayer官方庫 https://github.com/Bilibili/ijkplayer Vitamio官方庫 https://github.com/yixia/VitamioBundle 以jiecao爲例的封裝庫 https://github.com/JasonChow1989/JieCaoVideoPlayer-develop 2年前 https://github.com/open-android/JieCaoVideoPlayer 1年前 https://github.com/lipangit/JiaoZiVideoPlayer 4個月前 https://github.com/CarGuo/GSYVideoPlayer 其餘庫 https://github.com/danylovolokh/VideoPlayerManager
-app
1.2.2 具備的優點框架
<?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"> <!--https://github.com/yangchong211--> <!--若是你以爲好,請給個star,讓更多人使用,避免重複造輪子--> <!--底圖,主要是顯示視頻縮略圖--> <ImageView android:id="@+id/image" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" android:visibility="visible"/> <!--加載動畫view--> <include layout="@layout/custom_video_player_loading"/> <!--改變播放位置--> <include layout="@layout/custom_video_player_change_position"/> <!--改變亮度--> <include layout="@layout/custom_video_player_change_brightness"/> <!--改變聲音--> <include layout="@layout/custom_video_player_change_volume"/> <!--播放完成,你也能夠自定義--> <include layout="@layout/custom_video_player_completed"/> <!--播放錯誤--> <include layout="@layout/custom_video_player_error"/> <!--頂部控制區--> <include layout="@layout/custom_video_player_top"/> <!--底部控制區--> <include layout="@layout/custom_video_player_bottom"/> <!--右下角初始顯示的總時長--> <TextView android:id="@+id/length" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentEnd="true" android:layout_marginBottom="12dp" android:layout_marginEnd="8dp" android:padding="4dp" android:visibility="visible" android:text="00:00" android:textColor="@android:color/white" android:textSize="12sp"/> <!--中間開始播放按鈕--> <ImageView android:id="@+id/center_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/ic_player_center_start" android:visibility="visible"/> <!--試看按鈕--> <ImageView android:id="@+id/iv_try_see" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/selector_try_see" android:visibility="gone"/> <!--試看佈局,非會員顯示該佈局--> <include layout="@layout/custom_video_player_try_see"/> </RelativeLayout>
compile 'cn.yc:YCVideoPlayerLib:2.2'
<org.yczbj.ycvideoplayerlib.VideoPlayer android:id="@+id/video_player" android:layout_width="match_parent" android:layout_height="240dp"/>
//設置播放類型 // IjkPlayer or MediaPlayer videoPlayer1.setPlayerType(VideoPlayer.TYPE_NATIVE); //網絡視頻地址 String videoUrl = DataUtil.getVideoListData().get(0).getVideoUrl(); //設置視頻地址和請求頭部 videoPlayer1.setUp(videoUrl, null); //是否從上一次的位置繼續播放 videoPlayer1.continueFromLastPosition(true); //設置播放速度 videoPlayer1.setSpeed(1.0f); //建立視頻控制器 VideoPlayerController controller = new VideoPlayerController(this); controller.setTitle("辦快來圍觀拉,自定義視頻播放器能夠播放視頻拉"); //設置視頻時長 controller.setLength(98000); //設置5秒不操做後則隱藏頭部和底部佈局視圖 controller.setHideTime(5000); //controller.setImage(R.drawable.image_default); ImageUtil.loadImgByPicasso(this, R.drawable.image_default, R.drawable.image_default, controller.imageView()); //設置視頻控制器 videoPlayer1.setController(controller);
//設置視頻加載緩衝時加載窗的類型,多種類型 controller.setLoadingType(2); ArrayList<String> content = new ArrayList<>(); content.add("試看結束,yc觀看所有內容請開通會員1111。"); content.add("試看結束,yc觀看所有內容請開通會員2222。"); content.add("試看結束,yc觀看所有內容請開通會員3333。"); content.add("試看結束,yc觀看所有內容請開通會員4444。"); controller.setMemberContent(content); controller.setHideTime(5000); //設置設置會員權限類型,第一個參數是否登陸,第二個參數是否有權限看,第三個參數試看完後展現的文字內容,第四個參數是否保存進度位置 controller.setMemberType(false,false,3,true); controller.imageView().setBackgroundResource(R.color.blackText); //ImageUtil.loadImgByPicasso(this, R.color.blackText, R.drawable.image_default, controller.imageView()); //設置試看結束後,登陸或者充值會員按鈕的點擊事件 controller.setOnMemberClickListener(new OnMemberClickListener() { @Override public void onClick(int type) { switch (type){ case ConstantKeys.Gender.LOGIN: //調到用戶登陸也米娜 startActivity(MeLoginActivity.class); break; case ConstantKeys.Gender.MEMBER: //調到用戶充值會員頁面 startActivity(MeMemberActivity.class); break; default: break; } } });
@Override protected void onStop() { super.onStop(); VideoPlayerManager.instance().releaseVideoPlayer(); } @Override public void onBackPressed() { if (VideoPlayerManager.instance().onBackPressed()) return; super.onBackPressed(); }
//在宿主Activity中設置代碼以下 @Override protected void onStop() { super.onStop(); VideoPlayerManager.instance().releaseVideoPlayer(); } @Override public void onBackPressed() { if (VideoPlayerManager.instance().onBackPressed()) return; super.onBackPressed(); } //-------------------------------------------------- //在此Fragment中設置代碼以下 @Override public void onStop() { super.onStop(); VideoPlayerManager.instance().releaseVideoPlayer(); }
<activity android:name=".ui.test2.TestMyActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:screenOrientation="portrait"/>
recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setHasFixedSize(true); VideoAdapter adapter = new VideoAdapter(this, DataUtil.getVideoListData()); recyclerView.setAdapter(adapter); //注意:下面這個方法不能漏掉 recyclerView.setRecyclerListener(new RecyclerView.RecyclerListener() { @Override public void onViewRecycled(RecyclerView.ViewHolder holder) { VideoPlayer videoPlayer = ((VideoAdapter.VideoViewHolder) holder).mVideoPlayer; if (videoPlayer == VideoPlayerManager.instance().getCurrentVideoPlayer()) { VideoPlayerManager.instance().releaseVideoPlayer(); } } });
public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoViewHolder> { private Context mContext; private List<Video> mVideoList; VideoAdapter(Context context, List<Video> videoList) { mContext = context; mVideoList = videoList; } @Override public VideoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_test_my_video, parent, false); VideoViewHolder holder = new VideoViewHolder(itemView); //建立視頻播放控制器,主要只要建立一次就能夠呢 VideoPlayerController controller = new VideoPlayerController(mContext); holder.setController(controller); return holder; } @Override public void onBindViewHolder(VideoViewHolder holder, int position) { Video video = mVideoList.get(position); holder.bindData(video); } @Override public int getItemCount() { return mVideoList==null ? 0 : mVideoList.size(); } class VideoViewHolder extends RecyclerView.ViewHolder { VideoPlayerController mController; VideoPlayer mVideoPlayer; VideoViewHolder(View itemView) { super(itemView); mVideoPlayer = (VideoPlayer) itemView.findViewById(R.id.nice_video_player); // 將列表中的每一個視頻設置爲默認16:9的比例 ViewGroup.LayoutParams params = mVideoPlayer.getLayoutParams(); // 寬度爲屏幕寬度 params.width = itemView.getResources().getDisplayMetrics().widthPixels; // 高度爲寬度的9/16 params.height = (int) (params.width * 9f / 16f); mVideoPlayer.setLayoutParams(params); } /** * 設置視頻控制器參數 * @param controller 控制器對象 */ void setController(VideoPlayerController controller) { mController = controller; mVideoPlayer.setController(mController); } void bindData(Video video) { mController.setTitle(video.getTitle()); mController.setLength(video.getLength()); Glide.with(itemView.getContext()) .load(video.getImageUrl()) .placeholder(R.drawable.image_default) .crossFade() .into(mController.imageView()); mVideoPlayer.setUp(video.getVideoUrl(), null); } } }
if (videoPlayer.isIdle()) { Toast.makeText(this, "要點擊播放後才能進入小窗口", Toast.LENGTH_SHORT).show(); } else { videoPlayer.enterTinyWindow(); }
//若是不想打印庫中的日誌,能夠設置 VideoLogUtil.isLog = false;
public class VideoLogUtil { private static final String TAG = "YCVideoPlayer"; public static boolean isLog = true; static void d(String message) { if(isLog){ Log.d(TAG, message); } } static void i(String message) { if(isLog){ Log.i(TAG, message); } } static void e(String message, Throwable throwable) { if(isLog){ Log.e(TAG, message, throwable); } } }
//設置播放類型 // IjkPlayer or MediaPlayer videoPlayer1.setPlayerType(VideoPlayer.TYPE_NATIVE); //設置視頻地址和請求頭部 videoPlayer1.setUp(videoUrl, null); //建立視頻控制器 VideoPlayerController controller = new VideoPlayerController(this); //設置視頻控制器 videoPlayer1.setController(controller);
//設置播放類型 // MediaPlayer videoPlayer.setPlayerType(VideoPlayer.TYPE_NATIVE); // IjkPlayer videoPlayer.setPlayerType(VideoPlayer.TYPE_IJK); //網絡視頻地址 String videoUrl = DataUtil.getVideoListData().get(1).getVideoUrl(); //設置視頻地址和請求頭部 videoPlayer.setUp(videoUrl, null); //是否從上一次的位置繼續播放 videoPlayer.continueFromLastPosition(false); //設置播放速度 videoPlayer.setSpeed(1.0f); //設置播放位置 //videoPlayer.seekTo(3000); //設置音量 videoPlayer.setVolume(50); //設置全屏播放 videoPlayer.enterFullScreen(); //設置小屏幕播放 videoPlayer.enterTinyWindow(); //退出全屏 videoPlayer.exitFullScreen(); //退出小窗口播放 videoPlayer.exitTinyWindow(); //釋放,內部的播放器被釋放掉,同時若是在全屏、小窗口模式下都會退出 videoPlayer.release(); //釋放播放器,注意必定要判斷對象是否爲空,加強嚴謹性 videoPlayer.releasePlayer();
//是否從上一次的位置繼續播放,沒必要須 videoPlayer.continueFromLastPosition(false); //獲取最大音量 int maxVolume = videoPlayer.getMaxVolume(); //獲取音量值 int volume = videoPlayer.getVolume(); //獲取持續時長 long duration = videoPlayer.getDuration(); //獲取播放位置 long currentPosition = videoPlayer.getCurrentPosition(); //獲取緩衝區百分比 int bufferPercentage = videoPlayer.getBufferPercentage(); //獲取播放速度 float speed = videoPlayer.getSpeed(1);
//開始播放 videoPlayer.start(); //開始播放,從某位置播放 videoPlayer.start(3000); //從新播放 videoPlayer.restart(); //暫停播放 videoPlayer.pause();
//判斷是否開始播放 boolean idle = videoPlayer.isIdle(); //判斷視頻是否播放準備中 boolean preparing = videoPlayer.isPreparing(); //判斷視頻是否準備就緒 boolean prepared = videoPlayer.isPrepared(); //判斷視頻是否正在緩衝 boolean bufferingPlaying = videoPlayer.isBufferingPlaying(); //判斷是不是否緩衝暫停 boolean bufferingPaused = videoPlayer.isBufferingPaused(); //判斷視頻是否暫停播放 boolean paused = videoPlayer.isPaused(); //判斷視頻是否正在播放 boolean playing = videoPlayer.isPlaying(); //判斷視頻是否播放錯誤 boolean error = videoPlayer.isError(); //判斷視頻是否播放完成 boolean completed = videoPlayer.isCompleted(); //判斷視頻是否播放全屏 boolean fullScreen = videoPlayer.isFullScreen(); //判斷視頻是否播放小窗口 boolean tinyWindow = videoPlayer.isTinyWindow(); //判斷視頻是否正常播放 boolean normal = videoPlayer.isNormal();
//建立視頻控制器 VideoPlayerController controller = new VideoPlayerController(this); //設置視頻標題 controller.setTitle("高仿優酷視頻播放頁面"); //設置視頻時長 //controller.setLength(98000); //設置視頻加載緩衝時加載窗的類型,多種類型 controller.setLoadingType(2); ArrayList<String> content = new ArrayList<>(); content.add("試看結束,觀看所有內容請開通會員1111。"); content.add("試看結束,觀看所有內容請開通會員2222。"); content.add("試看結束,觀看所有內容請開通會員3333。"); content.add("試看結束,觀看所有內容請開通會員4444。"); //設置會員權限話術內容 controller.setMemberContent(content); //設置不操做後,5秒自動隱藏頭部和底部佈局 controller.setHideTime(5000); //設置設置會員權限類型,第一個參數是否登陸,第二個參數是否有權限看,第三個參數試看完後展現的文字內容,第四個參數是否保存進度位置 controller.setMemberType(false,false,3,true); //設置背景圖片 controller.imageView().setBackgroundResource(R.color.blackText); //ImageUtil.loadImgByPicasso(this, R.color.blackText, R.drawable.image_default, controller.imageView()); //設置試看結束後,登陸或者充值會員按鈕的點擊事件 controller.setOnMemberClickListener(new OnMemberClickListener() { @Override public void onClick(int type) { switch (type){ case ConstantKeys.Gender.LOGIN: //調到用戶登陸也米娜 startActivity(MeLoginActivity.class); break; case ConstantKeys.Gender.MEMBER: //調到用戶充值會員頁面 startActivity(MeMemberActivity.class); break; default: break; } } }); //設置視頻清晰度 //videoPlayer.setClarity(list,720); //設置視頻控制器 videoPlayer.setController(controller);
@Override public void release() { // 保存播放位置 if (isPlaying() || isBufferingPlaying() || isBufferingPaused() || isPaused()) { VideoPlayerUtils.savePlayPosition(mContext, mUrl, getCurrentPosition()); } else if (isCompleted()) { //若是播放完成,則保存播放位置爲0,也就是初始位置 VideoPlayerUtils.savePlayPosition(mContext, mUrl, 0); } // 退出全屏或小窗口 if (isFullScreen()) { exitFullScreen(); } if (isTinyWindow()) { exitTinyWindow(); } mCurrentMode = MODE_NORMAL; // 釋放播放器 releasePlayer(); // 恢復控制器 if (mController != null) { mController.reset(); } // gc回收 Runtime.getRuntime().gc(); } //釋放播放器,注意必定要判斷對象是否爲空,加強嚴謹性 @Override public void releasePlayer() { if (mAudioManager != null) { //放棄音頻焦點。使之前的焦點全部者(若是有的話)接收焦點。 mAudioManager.abandonAudioFocus(null); //置空 mAudioManager = null; } if (mMediaPlayer != null) { //釋放視頻焦點 mMediaPlayer.release(); mMediaPlayer = null; } //從視圖中移除TextureView mContainer.removeView(mTextureView); if (mSurface != null) { mSurface.release(); mSurface = null; } //若是SurfaceTexture不爲null,則釋放 if (mSurfaceTexture != null) { mSurfaceTexture.release(); mSurfaceTexture = null; } //設置狀態 mCurrentState = STATE_IDLE; }
Picasso.with(this) .load("http://jzvd-pic.nathen.cn/jzvd-pic/1bb2ebbe-140d-4e2e-abd2-9e7e564f71ac.png") .into(jzVideo.thumbImageView);
void setOnPreparedListener(IMediaPlayer.OnPreparedListener var1); void setOnCompletionListener(IMediaPlayer.OnCompletionListener var1); void setOnBufferingUpdateListener(IMediaPlayer.OnBufferingUpdateListener var1); void setOnSeekCompleteListener(IMediaPlayer.OnSeekCompleteListener var1); void setOnVideoSizeChangedListener(IMediaPlayer.OnVideoSizeChangedListener var1); void setOnErrorListener(IMediaPlayer.OnErrorListener var1); void setOnInfoListener(IMediaPlayer.OnInfoListener var1); void setOnTimedTextListener(IMediaPlayer.OnTimedTextListener var1);
//設置全屏播放 videoPlayer.enterFullScreen(); //設置小屏幕播放 videoPlayer.enterTinyWindow(); //退出全屏 videoPlayer.exitFullScreen(); //退出小窗口播放 videoPlayer.exitTinyWindow(); //釋放,內部的播放器被釋放掉,同時若是在全屏、小窗口模式下都會退出 videoPlayer.release(); //釋放播放器,注意必定要判斷對象是否爲空,加強嚴謹性 videoPlayer.releasePlayer();
public class VideoPlayerManager { private VideoPlayer mVideoPlayer; private static VideoPlayerManager sInstance; private VideoPlayerManager() {} //必定要使用單例模式,保證同一時刻只有一個視頻在播放,其餘的都是初始狀態 public static synchronized VideoPlayerManager instance() { if (sInstance == null) { sInstance = new VideoPlayerManager(); } return sInstance; } public VideoPlayer getCurrentVideoPlayer() { return mVideoPlayer; } void setCurrentVideoPlayer(VideoPlayer videoPlayer) { if (mVideoPlayer != videoPlayer) { releaseVideoPlayer(); mVideoPlayer = videoPlayer; } } //當視頻正在播放或者正在緩衝時,調用該方法暫停視頻 public void suspendVideoPlayer() { if (mVideoPlayer != null && (mVideoPlayer.isPlaying() || mVideoPlayer.isBufferingPlaying())) { mVideoPlayer.pause(); } } //當視頻暫停時或者緩衝暫停時,調用該方法從新開啓視頻播放 public void resumeVideoPlayer() { if (mVideoPlayer != null && (mVideoPlayer.isPaused() || mVideoPlayer.isBufferingPaused())) { mVideoPlayer.restart(); } } //釋放,內部的播放器被釋放掉,同時若是在全屏、小窗口模式下都會退出 public void releaseVideoPlayer() { if (mVideoPlayer != null) { mVideoPlayer.release(); mVideoPlayer = null; } } //處理返回鍵邏輯.若是是全屏,則退出全屏 若是是小窗口,則退出小窗口 public boolean onBackPressed() { if (mVideoPlayer != null) { if (mVideoPlayer.isFullScreen()) { return mVideoPlayer.exitFullScreen(); } else if (mVideoPlayer.isTinyWindow()) { return mVideoPlayer.exitTinyWindow(); } } return false; } }
//建立視頻控制器 VideoPlayerController controller = new VideoPlayerController(this); //設置視頻標題 controller.setTitle("高仿優酷視頻播放頁面"); //設置視頻時長 //controller.setLength(98000); //設置視頻加載緩衝時加載窗的類型,多種類型 controller.setLoadingType(2); ArrayList<String> content = new ArrayList<>(); content.add("試看結束,觀看所有內容請開通會員1111。"); content.add("試看結束,觀看所有內容請開通會員2222。"); content.add("試看結束,觀看所有內容請開通會員3333。"); content.add("試看結束,觀看所有內容請開通會員4444。"); //設置會員權限話術內容 controller.setMemberContent(content); //設置不操做後,5秒自動隱藏頭部和底部佈局 controller.setHideTime(5000); //設置設置會員權限類型,第一個參數是否登陸,第二個參數是否有權限看,第三個參數試看完後展現的文字內容,第四個參數是否保存進度位置 controller.setMemberType(false,false,3,true); //設置背景圖片 controller.imageView().setBackgroundResource(R.color.blackText); //ImageUtil.loadImgByPicasso(this, R.color.blackText, R.drawable.image_default, controller.imageView()); //設置試看結束後,登陸或者充值會員按鈕的點擊事件 controller.setOnMemberClickListener(new OnMemberClickListener() { @Override public void onClick(int type) { switch (type){ case ConstantKeys.Gender.LOGIN: //調到用戶登陸也米娜 startActivity(MeLoginActivity.class); break; case ConstantKeys.Gender.MEMBER: //調到用戶充值會員頁面 startActivity(MeMemberActivity.class); break; default: break; } } }); //設置視頻清晰度 //videoPlayer.setClarity(list,720); //設置視頻控制器 videoPlayer.setController(controller);
/** * 設置視頻Url,以及headers * * @param url 視頻地址,能夠是本地,也能夠是網絡視頻 * @param headers 請求header. */ void setUp(String url, Map<String, String> headers); /** * 開始播放 */ void start(); /** * 從指定的位置開始播放 * * @param position 播放位置 */ void start(long position); /** * 從新播放,播放器被暫停、播放錯誤、播放完成後,須要調用此方法從新播放 */ void restart(); /** * 暫停播放 */ void pause(); /** * seek到制定的位置繼續播放 * * @param pos 播放位置 */ void seekTo(long pos); /** * 設置音量 * * @param volume 音量值 */ void setVolume(int volume); /** * 設置播放速度,目前只有IjkPlayer有效果,原生MediaPlayer暫不支持 * * @param speed 播放速度 */ void setSpeed(float speed); /** * 開始播放時,是否從上一次的位置繼續播放 * * @param continueFromLastPosition true 接着上次的位置繼續播放,false從頭開始播放 */ void continueFromLastPosition(boolean continueFromLastPosition);
public class YCVideoPlayer extends VideoPlayer { public YCVideoPlayer(Context context) { super(context); } @Override public void setUp(String url, Map<String, String> headers) { super.setUp(url, headers); } @Override public void setController(AbsVideoPlayerController controller) { super.setController(controller); } @Override public void setPlayerType(int playerType) { super.setPlayerType(playerType); } @Override public void continueFromLastPosition(boolean continueFromLastPosition) { super.continueFromLastPosition(continueFromLastPosition); } @Override public void setSpeed(float speed) { super.setSpeed(speed); } @Override public void start() { super.start(); } @Override public void start(long position) { super.start(position); } @Override public void restart() { super.restart(); } @Override public void pause() { super.pause(); } @Override public void seekTo(long pos) { super.seekTo(pos); } @Override public void setVolume(int volume) { super.setVolume(volume); } @Override public boolean isIdle() { return super.isIdle(); } @Override public boolean isPreparing() { return super.isPreparing(); } @Override public boolean isPrepared() { return super.isPrepared(); } @Override public boolean isBufferingPlaying() { return super.isBufferingPlaying(); } @Override public boolean isBufferingPaused() { return super.isBufferingPaused(); } @Override public boolean isPlaying() { return super.isPlaying(); } @Override public boolean isPaused() { return super.isPaused(); } @Override public boolean isError() { return super.isError(); } }
https://github.com/CarGuo/GSYVideoPlayer https://github.com/danylovolokh/VideoPlayerManager https://github.com/HotBitmapGG/bilibili-android-client https://github.com/jjdxmashl/jjdxm_ijkplayer https://github.com/JasonChow1989/JieCaoVideoPlayer-develop 2年前 https://github.com/open-android/JieCaoVideoPlayer 1年前 https://github.com/lipangit/JiaoZiVideoPlayer 4個月前 https://github.com/xiaoyanger0825/NiceVieoPlayer https://github.com/curtis2/SuperVideoPlayer https://github.com/tcking/GiraffePlayer
https://segmentfault.com/a/1190000011959615 http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1213/2153.html http://blog.csdn.net/junwang19891012/article/details/8444743 https://www.jianshu.com/p/420f7b14d6f6 http://blog.csdn.net/candicelijx/article/details/39495271
1.1 VideoView 的使用很是簡單,播放視頻的步驟:
調用 VideoView 的以下兩個方法來加載指定的視頻:
2.2 用法
ExoPlayer 開源項目包含了 library 和 示例:
ExtractorSampleSource – 用於 MP3,M4A,WebM,MPEG-TS 和 AAC;
在 ExoPlayer 的 Dome 中使用 DemoPlayer 對 ExoPlayer 進行了封裝,並提供了使用上述幾種 SampleSource 構建 TrackRenderer 的 Builder。
ExoPlayer 相較於 MediaPlayer 有不少不少的優勢:
ExoPlayer 的缺點:
Vitamio 的使用步驟:
3.2 優勢
4.1 特色
4.2 優缺點