2014年2月18日 星期二

JavaFX Media (1) Introduction

除了AudioClip類別之外,欲播放音訊與影像媒體,可使用以下類別處理:
  • javafx.scene.media.Media
  • javafx.scene.media.MediaPlayer
  • javafx.scene.media.MediaView
其中Media類別用以設定音訊與影像來源,可為URL或本機檔案。此外,Media類別並處理音訊與影像的時間長度、後設資料、標記等資訊。Media類別的建構函式如下,其中參數source設定音訊或影像來源,可為URL或本機檔案:

  public Media(String source)

Media類別設定音訊與影像來源之後,則交由MediaPlayer類別處理,如同其名稱Media Player (媒體播放器) 一般,用以播放、暫停、重覆或停止播放媒體,此外,MediaPlayer類別並處理調整音量平衡、重覆播放次數、播放速率、音量大小、時間長度、頻譜等資訊。MediaPlayer類別的建構函式如下,其中參數mediaMedia類別所建立的音訊或影像物件:

  public MediaPlayer(Media media)

MediaView類別如同TableViewTreeViewWebView一般,用以「顯示」影像媒體。MediaView類別的建構函式如下,其中參數mediaPlayerMediaPlayer類別所建立之物件:

  public MediaView()
  public MediaView(MediaPlayer mediaPlayer)

在實作上若處理音訊,則僅需使用MediaMediaPlayer類別;若處理影像,則需以MediaMediaPlayerMediaView類別三者搭配一起使用。

請參考以下範例,以MediaMediaPlayer類別播放音訊,首先以Media類別設定音訊來源,可為URL或本機檔案。接著以MediaPlayer類別的setAutoPlay()方法自動播放音訊:


Media media; 
MediaPlayer mediaplayer; 
... 

try {
  String directory = System.getProperty("user.dir"); 
  File file = new File(directory, "../../media/Audio.mp3"); 

  media = new Media(file.toURI().toString()); 
  mediaplayer = new MediaPlayer(media); 

  // 自動播放音訊 
  mediaplayer.setAutoPlay(true); 

catch (Exception ex) { 
  ex.toString(); 
}


請參考以下範例,以MediaMediaPlayerMediaView類別播放影像,首先以Media類別設定影像來源,可為URL或本機檔案。接著以MediaPlayer類別建立MediaPlayer物件,並由MediaView類別「顯示」影像媒體。最後再以MediaPlayer類別的play()方法播放影像:


Media media; 
MediaPlayer mediaplayer; 
MediaView mediaview; 
... 

try { 
  String directory = System.getProperty("user.dir"); 
  File file = new File(directory, "../../media/Video.mp4"); 

  media = new Media(file.toURI().toString()); 
  mediaplayer = new MediaPlayer(media); 
  mediaview = new MediaView(mediaplayer); 

catch (Exception ex) { 
  ex.toString(); 

... 

BorderPane borderpane = new BorderPane(); borderpane.setCenter(mediaview); 
... 

// 播放影像 
mediaplayer.play();


為免影像因尺寸太大而超出視窗範圍,因此以MediaView類別的setFitHeight()setFitWidth()方法分別設定影像的適合 (Fit) 高度與寬度,以便讓影像符合視窗範圍。 

此外,並以heightProperty().addListener()widthProperty().addListener()方法分別處理視窗高度與寬度的改變事件,當視窗高度或寬度改變時,同時調整影像的高度或寬度:


mediaview.setFitHeight(primaryStage.getHeight()); mediaview.setFitWidth(primaryStage.getWidth());

primaryStage.heightProperty().addListener(new ChangeListener() {   
  @Override public void changed(ObservableValue observable, 
    Number oldValue, Number newValue) {

    mediaview.setFitHeight((Double)newValue); 
  } 
}); 

primaryStage.widthProperty().addListener(new ChangeListener() { 
  @Override public void changed(ObservableValue observable, 
    Number oldValue, Number newValue) { 

    mediaview.setFitWidth((Double)newValue); 
  } 
});



請參考以下範例,範例加入標籤與滑動軸處理音訊,前者處理播放、暫停、向前與倒轉,後者調整時間長度、音量大小與音量平衡。 

以播放與暫停為例,當點選播放標籤時,首先以MediaPlayer類別的getStatus()方法取得媒體目前的狀態,其回傳值分別為: 
  • MediaPlayer.Status.DISPOSED:當執行dispose()方法釋放資源時。 
  • MediaPlayer.Status.HALTED:錯誤發生時。 
  • MediaPlayer.Status.PAUSED:暫停播放時。 
  • MediaPlayer.Status.PLAYING:正在播放時。 
  • MediaPlayer.Status.READY:媒體準備播放時。 
  • MediaPlayer.Status.STALLED:當無法處理媒體致使播放速度變慢或停止時。
  • MediaPlayer.Status.STOPPED:停止播放時。 
  • MediaPlayer.Status.UNKNOWN:MediaPlayer物件剛建立時。 
若狀態為正在播放時 (MediaPlayer.Status.PLAYING),則以MediaPlayer類別的pause()方法暫停播放;反之則以play()方法播放音訊:


Label lblPlay = new Label(""); 
...
lblPlay.setOnMouseClicked(new EventHandler() { 
  @Override public void handle(MouseEvent e) { 
    // 取得媒體目前的狀態 
    if (mediaplayer.getStatus() == MediaPlayer.Status.PLAYING) { 
      // 暫停播放 
      mediaplayer.pause(); 
    } 
    else { 
      // 播放音訊 
      mediaplayer.play(); 
    } 
   } 
}); 


以向前播放 (Forward) 為例,當點選向前標籤時,首先以MediaPlayer類別的getTotalDuration()方法取得音訊的總時間,接著以seek()方法移至音訊的最後,並藉此調整滑動軸的位置:


Label lblForward = new Label(""); 
...
lblForward.setOnMouseClicked(new EventHandler() { 
  @Override public void handle(MouseEvent e) { 
    // 取得音訊的總時間 
    final Duration totalDuration = mediaplayer.getTotalDuration(); 
    // 取得音訊目前的狀態 
    if (mediaplayer.getStatus() == MediaPlayer.Status.STOPPED) {
      mediaplayer.pause(); 
    } 
    // 移至音訊的最後 
    mediaplayer.seek(totalDuration); 
    // 取得音訊目前的狀態 
    if (mediaplayer.getStatus() != MediaPlayer.Status.PLAYING) { 
      if (sliderDuration.isValueChanging()) 
        return; 

      final Duration total = mediaplayer.getTotalDuration(); 

      if (total == null || totalDuration == null) { 
        // 調整滑動軸的位置 
        sliderDuration.setValue(0); 
      } 
      else { 
        // 調整滑動軸的位置 
        sliderDuration.setValue(  
          totalDuration.toMillis() / total.toMillis()); 
      } 
    } 
  } 
});



倒轉播放 (Rewind) 類似於向前播放,差別僅在於當點選倒轉標籤時,首先將音訊的目前進度時間設為Duration.ZERO (歸零),接著以seek()方法移至音訊的最前端,並藉此調整滑動軸的位置:


Label lblRewind = new Label(""); 
...
lblRewind.setOnMouseClicked(new EventHandler() {  
  @Override public void handle(MouseEvent e) {   
    // 設定目前的進度時間為零   
    final Duration currentduration = Duration.ZERO;   
    // 取得音訊目前的狀態   
    if (mediaplayer.getStatus() == MediaPlayer.Status.STOPPED) {  
      mediaplayer.pause();   
    }   
    // 移至音訊的最前端   
    mediaplayer.seek(currentduration);   
    // 取得音訊目前的狀態   
    if (mediaplayer.getStatus() != MediaPlayer.Status.PLAYING) {   
      if (sliderDuration.isValueChanging())   
        return; 

      final Duration total = mediaplayer.getTotalDuration();   

      if (total == null) {   
        // 調整滑動軸的位置   
        sliderDuration.setValue(0);   
      }   
      else {   
        // 調整滑動軸的位置   
        sliderDuration.setValue(   
          currentduration.toMillis() / total.toMillis());   
      }   
    }   
  
}); 


以滑動軸調整音量大小 (Volume) 為例,首先以Slider類別的valueProperty()方法取得滑動軸的值屬性,接著以bindBidirectional()方法與MediaPlayer類別的volumeProperty()建立雙向繫結 (Bidirectional Binding),當移動滑動軸時,則以滑動軸的值設定音訊的音量大小:


Slider sliderVolume = new Slider(); 
sliderVolume.setMin(0.0); 
sliderVolume.setMax(1.0); 
sliderVolume.setValue(1.0); 
sliderVolume.setPrefWidth(60); 
sliderVolume.setMaxWidth(Region.USE_PREF_SIZE); 
sliderVolume.setMinWidth(30); 

// 建立滑動軸與音訊音量的雙向繫結 
sliderVolume.valueProperty().bindBidirectional(
  mediaplayer.volumeProperty());


以滑動軸調整音量平衡 (Balance) 為例,首先以Slider類別的valueProperty()方法取得滑動軸的值屬性,接著以addListener()方法處理滑動軸事件,當移動滑動軸時,則以滑動軸的值設定音訊的音量平衡:


final Slider sliderBalance = new Slider(); 
sliderBalance.setMin(-1.0); 
sliderBalance.setMax(1.0); 
sliderBalance.setValue(0.0); 
sliderBalance.setPrefWidth(60); sliderBalance.setMaxWidth(Region.USE_PREF_SIZE); 
sliderBalance.setMinWidth(30); 
sliderBalance.valueProperty().addListener(new InvalidationListener() {
  @Override public void invalidated(Observable observable) { 
    if (sliderBalance.isValueChanging()) { 
       // 設定音訊的音量平衡
       mediaplayer.setBalance(sliderBalance.getValue()); 
    } 
  } 
});


【執行結果】
請參考以下範例,加入標籤與滑動軸處理影像,前者處理播放、暫停、向前與倒轉,後者調整時間長度、音量大小與音量平衡,程式與前述範例幾乎一樣,差別僅在於,以MediaPlayer類別所建立MediaPlayer物件,需由MediaView類別「顯示」影像媒體。

【執行結果】
【參考資料】

[1] 黃嘉輝,深入研究JavaFX 2。
[2] 黃嘉輝,深入研究Java Swing。
[3] Java Official Web Site:http://www.oracle.com/technetwork/java/index.html
[4] JavaFX:http://www.oracle.com/technetwork/java/javafx
[5] JavaFX 2.2 API Specification.
[6] Java Platform, Standard Edition 7 API Specification.

© Chia-Hui Huang 

沒有留言:

張貼留言