0

Tạo ứng dụng chia sẻ video với Ruby on Rails

Trong hướng dẫn này, bạn sẽ biết cách tạo ứng dụng chia sẻ video cơ bản sử dụng Rails.

Các tính năng bao gồm:

  • Sign up, in, out - sử dụng gem devise.
  • Upload video - xử lý mã hoá video.
  • Play video - sử dụng videojs tạo trình chạy video đơn giản.
  • Thông báo - thông báo cho người dùng khi mã hoá xong video, sử dụng pubnub.

Để thực hiện, bạn cần một số kiến thức về Ruby on Rails, CoffeeScriptHAML. Biết cách cài đặt ffmpegredis.

Khởi tạo

Tạo mới một ứng dụng Rails

$ rails new VideoShrimp

Chúng ta sẽ sử dụng cơ sở dữ liệu SQLite, tất nhiên bạn có thể sử dụng postgresql, mysql hay bất cứ cơ sở dữ liệu nào bạn muốn.

Sử dụng Rails version 4.1 trở lên:

$ rails -v
Rails 4.2.4
$ ruby -v
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin15]

Tiếp theo là thêm các gem cần dùng vào Gemfile.

gem 'haml'
gem 'devise'
gem 'simple_form'
gem 'paperclip'
gem 'bootstrap-sass'
gem 'sidekiq'
gem 'sidetiq', github: 'sfroehler/sidetiq', branch: 'celluloid-0-17-compatibility'
gem 'pubnub',  github: 'pubnub/ruby', branch: 'celluloid'
gem 'sinatra', :require => nil

group :development do
  gem 'pry'
  gem 'pry-rails'
end

Giải thích:

  • haml - tương tự template *html.erb.
  • devise - xác thực người dùng.
  • simple_form - thiết kế form chuẩn trong Rails.
  • paperclip - quản lý tệp.
  • bootstrap-sass - thiết kế giao diện.
  • sidekiq - xử lý video ở chế độ nền.
  • sidetiq - là một plugin của sidekiq. Dùng để kiểm tra những video đã được mã hoá mà chưa được công bố.
  • pubnub - để thông báo và lấy các thông tin liên lạc giữa backend-frontend.
  • sinatra - sidekiq frontend, tuỳ chọn.
  • pry - tốt hơn irb.
  • pry-rails - dùng pry thay cho irb trong rails c.

Chú ý #1: Như bạn thấy, tôi dùng gem sidekiq từ repository ngoài, vì gem chuẩn khồn tương thích với celluloid hiện tại (thời điểm viết bài).

Chú ý #2: gem pubnub lấy từ branch khác master vì bản celluloid chính chưa ổn định. Hiện dùng pubnub bản beta.

Tiếp theo, chạy bundle install để tải gem.

$ bundle install

Cài đặt devise và simple_form

$ rails generate simple_form:install --bootstrap
$ rails generate devise:install

Lệnh tạo devise sẽ tạo ra file config/initializers/devise.rbconfig/locales/devise.en.yml nhưng bạn không cần quan tâm đến hai file này, hiện tại không cần cấu hình thêm gì từ devise.

Tạo view để sử dụng simple_form:

$ rails generate devise:views

Tiếp theo là tạo model và migrate.

Tạo model

rails generate devise user
      invoke  active_record
      create    db/migrate/20151117181300_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

Với ứng dụng cơ bản này, chúng ta không cần thêm gì khác vì sẽ sử dụng user email mặc định để đăng ký.

Tạo video model

rails generate model Video name:string video_file:attachment mp4_file:attachment webm_file:attachment ogg_file:attachment thumbnail:attachment published:boolean likes:integer user:references

File migration sẽ có dạng:

class CreateVideos < ActiveRecord::Migration
  def change
    create_table :videos do |t|
      t.string :name
      t.attachment :video_file
      t.attachment :mp4_file
      t.attachment :webm_file
      t.attachment :ogg_file
      t.attachment :thumbnail
      t.boolean :published
      t.integer :likes, default: 0
      t.references :user, index: true, foreign_key: true

      t.timestamps null: false
    end
  end
end

Và giờ sẽ là viết code cho model.

class Video < ActiveRecord::Base
  # Association declaration
  belongs_to :user

  # Paperclip attachments declaration
  has_attached_file :video_file
  has_attached_file :mp4_file
  has_attached_file :webm_file
  has_attached_file :ogg_file
  # Styles declaration makes paperclip to use imagemagick to resize image to given size
  has_attached_file :thumbnail, styles: { medium_nr: "250x150!" }

  # Paperclip requires to set attachment validators
  validates_attachment_content_type :video_file, content_type: /\Avideo/
  validates_attachment_content_type :mp4_file, content_type: /.*/
  validates_attachment_content_type :webm_file, content_type: /.*/
  validates_attachment_content_type :ogg_file, content_type: /.*/

  # We want video model always to have :video_file attachment
  validates_attachment_presence :video_file

  # Publish video makes it available
  def publish!
    self.published = true
    save
  end

  # Increment likes counter
  def like!
    self.likes += 1
    save
  end

  # Decrease likes counter
  def dislike!
    self.likes -= 1
    save
  end

  # Checks if all formats are already encoded, the simplest way
  def all_formats_encoded?
    self.webm_file.path && self.mp4_file.path && self.ogg_file.path ? true : false
  end
end

Thêm relation cho video:

class User < ActiveRecord::Base
  # Association declaration
  has_many :videos

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
end

Chạy migration

$ rake db:migrate

Tạo controller và view

Bây giờ, chúng ta sẽ tạo controller và view cho ứng dụng, nên cho phép người dùng tạo tài khoản và đăng nhập. Thêm code cho file app/controllers/application_controller.rb như sau:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  layout :layout_by_resource

  protected

  def layout_by_resource
    if devise_controller?
      'devise'
    else
      'application'
    end
  end
end

Đoạn code trên kiểm tra controller có phải là của devise không, nếu đúng sẽ trả ra devise layout, nếu không sẽ trả ra layout chuẩn.

Tiếp tục với file app/views/layouts/devise.haml.

!!!
%html
  %head
    %title VideoShrimp
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true
    = javascript_include_tag 'application', 'data-turbolinks-track' => true
    = csrf_meta_tags
  %body{'data-no-turbolink': true}
    .container
      .row
        .col-md-4.col-md-offset-4
          %h1.text-center VideoShrimp
          = yield

Trong devise layout, có các thành phần cơ bản như container, .row, .col-md-4.col-md-offset-4 trong bootstrap. Nếu bạn không biết các thành phần này, tôi khuyến khích đọc về Bootstrap Grid System.

Bây giờ tạo users_controller.rb trong app/controllers.

class UsersController < ApplicationController
  # Checks if user is signed in before running controller, functionality provided by devise
  before_action :authenticate_user!

  before_action :set_user, only: [:show]
  before_action :set_current_user, only: [:edit, :update]

  def index
    @@users = User.all
  end

  def show
  end

  def update
    respond_to do |format|
      if @@user.update(user_params)
        format.html { redirect_to @@user, notice: 'User was successfully updated.' }
        format.json { render :show, status: :ok, location: @@user }
      else
        format.html { render :edit }
        format.json { render json: @@user.errors, status: :unprocessable_entity }
      end
    end
  end

  private
  def set_user
    @@user = User.find(params[:id])
  end

  def set_current_user
    @@user = current_user
  end

  def user_params
    params.require(:user).permit(:email)
  end
end

Controller trên dùng để hiển thị hồ sơ người dùng và cho phép thay đổi email. Tiếp tục với video controller

class VideosController < ApplicationController
  before_action :authenticate_user!
  before_action :set_video, only: [:show, :edit, :like, :dislike]

  # All published videos
  def index
    @@videos = Video.where(published: true)
  end

  def show
  end

  def new
    @@video = Video.new
  end

  def edit
  end

  def create
    @@video = Video.new(video_params)

    respond_to do |format|
      if @@video.save
        format.html { redirect_to @@video, notice: 'Video was successfully created.' }
        format.json { render :show, status: :created, location: @video }
      else
        format.html { render :new }
        format.json { render json: @@video.errors, status: :unprocessable_entity }
      end
    end
  end

  # Likes video, increment likes count
  def like
    @@video.like!
  end

  # Dislikes video, increment likes count
  def dislike
    @@video.dislike!
  end

  private
  def set_video
    @@video = Video.find(params[:id])
  end

  def video_params
    params.require(:video).permit(:video_file, :name)
  end
end

Khá đơn giản, phải không?

Bây giờ xem lại. Hãy loại bỏ file application.html.erb từ app/views/layouts và tạo application.haml.

!!!
%html
  %head
    %title VideoShrimp
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true
    = stylesheet_link_tag    '//vjs.zencdn.net/5.0.2/video-js.css'
    = javascript_include_tag 'application', 'data-turbolinks-track' => true
    = javascript_include_tag '//cdn.pubnub.com/pubnub-dev.js'
    = csrf_meta_tags
  %body{'data-no-turbolink': true}
    - if current_user
      .navbar.navbar-default.navbar-fixed-top
        .container
          .navbar-header
            = link_to 'VideoShrimp', root_url, class: 'navbar-brand'
          %ul.nav.navbar-nav.navbar-right
            %li
              = link_to 'Browse videos', videos_path
            %li
              = link_to 'Upload video', new_video_path
            %li.dropdown
              %a.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", :href => "#", :role => "button"}
                = current_user.email
                %span.caret
              %ul.dropdown-menu
                %li
                  = link_to 'Profile', current_user
                %li
                  = link_to 'Edit profile', edit_user_path(current_user)
                %li.divider{:role => "separator"}
                %li
                  = link_to 'Log out', destroy_user_session_path, :method => :delete

            %li#user-notifications.dropdown{"data-pn-auth-key": current_user.pn_auth_key, "data-pn-notification-channel": current_user.notification_channel}
              %a.dropdown-toggle{"aria-expanded" => "false", "aria-haspopup" => "true", "data-toggle" => "dropdown", :href => "#", :role => "button"}
                %span.glyphicon.glyphicon-bell
              %ul.dropdown-menu

    = yield

Đó là layout trống, với một số điều hướng cơ bản bootstrapped. Như vậy là còn thẻ cho pubnub mà chúng ta sẽ sử dụng để thông báo và videojs để chạy video.

Hãy thêm bootstrap cho stylesheets! Đầu tiên, gỡ bỏ app/assets/stylesheets/application.css và tạo app/assets/stylesheets/application.scss với:

@import "bootstrap-sprockets";
@import "bootstrap";
@import "global";
@import "video";

Tạo global.scssvideo.scss cũng tương tự. Bootstrap cần được thêm vào app/assets/javascripts/applications.js, trông sẽ thế này:

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require bootstrap
//= require_tree .

Điều cuối cùng thêm vào assets là sửa global.scss trong app/assets/stylesheets/ và thêm:

body {
  padding-top: 60px;
}

Nhờ đó nội dung trang web sẽ không bị ẩn dưới navigation.

Tất cả các công cụ này sẽ không làm việc? Tất nhiên, bởi vì chúng tôi đã không tạo ra các route. Vì vậy, hãy làm điều đó và chỉnh sửa file config/routes.rb như sau:

require 'sidekiq/web'
Rails.application.routes.draw do

  root 'videos#index'
  resources :videos

  get '/videos/:id/like' => 'videos#like'
  get '/videos/:id/dislike' => 'videos#dislike'

  devise_for :users

  resources :users

  mount Sidekiq::Web => '/sidekiq'
end

Vì vậy, trang chủ của ứng dụng sẽ là danh sách các video được tải lên. Resource cho video và user là quá mức cần thiết bởi vì chúng sẽ không được sử dụng một phần của route được tạo ra nhưng nếu bạn muốn mở rộng ứng dụng thì bạn sẽ cần nó. Sidekiq web là interface của sidekiq. Đừng quên require 'sidekiq/web' trên đầu file.

Hãy bắt đầu với user. Dưới app/views/users/ chúng ta sẽ tạo ra hai file: show.haml

.container
  .row
    .col-md-12
      %h2= @user.email
.container
  .row
    - @user.videos.each do |video|
      .col-md-3.thumb.video-thumb{ 'data-video-id': video.id }
        .likes
          %span.likes-count= video.likes
          %span.glyphicon.glyphicon-heart
        = link_to video, class: 'thumbnail' do
          = image_tag video.thumbnail.url(:medium_nr)

và edit.haml

.container
  .row
    .col-md-6.col-sm-12
      %h2 Edit profile

      = simple_form_for @user do |f|
        = f.input :email
        = f.button :submit, class: 'btn-primary', value: 'Update Profile'

Và giờ tạo video views, thêm vào thư mục app/views/videos index.haml

.container
  .row.video-full{ 'data-video-id': @video.id }
    .col-md-10
      %h1= @video.name
      %p.small
        = link_to 'Back to videos' ,videos_path

      - if @video.all_formats_encoded?
        %video#my-video.video-js{:controls => "", "data-setup" => "{}", :height => "264", :preload => "auto", :width => "640"}
          %source{:src => @video.mp4_file.url, :type => "video/mp4"}
          %source{:src => @video.webm_file.url, :type => "video/webm"}
          %source{:src => @video.ogg_file.url, :type => "video/ogg"}
          %p.vjs-no-js
            To view this video please enable JavaScript, and consider upgrading to a web browser that
            %a{:href => "http://videojs.com/html5-video-support/", :target => "_blank"} supports HTML5 video
      - else
        %p Video is still being encoded.
    .col-md-2
      %h1
        %span.likes-count= @video.likes
        %span.glyphicon.glyphicon-heart

Hiển thị video và cho phép thích hay không thích. Nó không cập nhật truy cập bởi vì ta sẽ sử dụng pubnub notification để làm điều đó. new.haml

.container
  .row
    .col-md-6.col-sm-12
      %h2 Upload new video

      = simple_form_for @video, html: { multipart: true } do |f|
        = f.input :name
        = f.input :video_file, as: :file
        = f.button :submit, class: 'btn-primary', value: 'Upload Video'

Vậy là đã có một form đơn giản để tải lên video. Sẽ có tên video và nút thêm tệp tin.

Xin chúc mừng! Một số nội dung đã sẵn sàng!

Mã hoá video và hiển thị thông báo

Bây giờ chúng ta cần phải mã hóa video được tải lên, hiển thị chúng cho người dùng và gửi thông báo. Hãy dùng pubnub để làm điều đó.

Đi đến trang web Pubnub và tạo tài khoản mới. Sau khi tạo tài khoản mới, bạn nên thêm ứng dụng mới và tạo ra các khoá cho ứng dụng. Chúng tôi sẽ sử dụng tính năng Storeage & Playback cho lịch sử thông báo và Access manager để làm thông báo tin nhắn.

Bạn sẽ cần phải sao chép các khoá của bạn vào file config/secrets.yml như pubnub_subscribe_key, pubnub_publish_key, pubnub_secret_key và bạn nên tạo cho mình một khoá pubnub_auth_key duy nhất cho máy chủ.

Bây giờ chúng ta cần thêm pubnub.rb trong config/initializers, code đó sẽ chạy khi ứng dụng được khởi động.

pubnub.rb

# Initialize pubnub client with our keys
$pubnub = Pubnub.new(
    subscribe_key: Rails.application.secrets.pubnub_subscribe_key,
    publish_key: Rails.application.secrets.pubnub_publish_key,
    secret_key: Rails.application.secrets.pubnub_secret_key,
    auth_key: Rails.application.secrets.pubnub_auth_key
)

# As we have PAM enabled, we have to grant access to channels.
# That grants read right to any channel that begins with 'video.' to everyone.
$pubnub.grant(
    read: true,
    write: false,
    auth_key: nil,
    channel: 'video.*',
    http_sync: true,
    ttl: 0
)

# That grants read and write right to any channel that begins with 'video.' to this client.
$pubnub.grant(
    read: true,
    write: true,
    auth_key: Rails.application.secrets.pubnub_auth_key,
    channel: 'video.*',
    http_sync: true,
    ttl: 0
)

# That grants read and write right to any channel that begins with 'notifications.' to this client.
$pubnub.grant(
    read: true,
    write: true,
    auth_key: Rails.application.secrets.pubnub_auth_key,
    channel: 'notifications.*',
    http_sync: true,
    ttl: 0
)

Như bạn thấy, sau khi khởi tạo biến toàn cục $pubnub, chúng tôi đang chạy 3 cấp với ttl: 0 - mà cấp sẽ không bao giờ hết hạn.

Tiếp theo, chúng ta phải cung cấp cho người dùng khả năng để đọc thông báo cá nhân của mình. Chúng tôi sẽ làm điều đó bằng cách tạo ra auth_key duy nhất và cấp quyền đọc kênh thông báo của mình cho mỗi người dùng. Hãy tạo migration:

$ rails generate migration add_pn_auth_key_to_users

Tạo migration như sau:

class AddPnAuthKeyToUsers < ActiveRecord::Migration
  def change
    add_column :users, :pn_auth_key, :string
  end
end

Tiếp tục, sửa file user.rb trong devise:

after_create :gen_auth_and_grant_perms

def notification_channel
  "notifications.#{self.id}"
end

def gen_auth_and_grant_perms
  generate_pn_auth!
  $pubnub.grant(
    channel: notification_channel,
    auth_key: pn_auth_key,
    ttl: 0,
    http_sync: true
  )
end

def generate_pn_auth
  self.pn_auth_key = SecureRandom.hex
end

def generate_pn_auth!
  self.generate_pn_auth
  save
end

Nó sẽ chạy method gen_auth_and_grant_perms sau khi người dùng được tạo ra. Người dùng sẽ nhận được auth_key duy nhất của mình sẽ được sử dụng bởi javascript client và được quyền để đọc trên kênh riêng của mình.

Bây giờ, ta sẽ sửa Video model. Chỉnh sửa video.rb và thực hiện thay đổi các method publish!, like!, dislike!.

# Publish video makes it available
def publish!
  self.published = true
  save
  $pubnub.publish(channel: "video.#{id}", message: {event: :published}, http_sync: true)
  $pubnub.publish(channel: self.user.notification_channel, message: {event: :published, scope: :videos, id: self.id, name: name.truncate(20)}, http_sync: true)
end

# Increment likes counter
def like!
  self.likes += 1
  save
  $pubnub.publish(channel: "video.#{id}", message: {event: :liked}, http_sync: true)
end

# Decrease likes counter
def dislike!
  self.likes -= 1
  save
  $pubnub.publish(channel: "video.#{id}", message: {event: :disliked}, http_sync: true)
end

Khi thích hoặc không thích sẽ gửi tin nhắn tương ứng đến kênh video vì vậy ta có thể cập nhật trực tiếp khi đăng ký kênh. Khi publish sẽ gửi thông báo cho chủ video rằng video đã được công bố trên kênh tương ứng.

Trước khi làm việc trên frontend, cần mã hoá video được tải lên. Trước tiên, tạo thư mục app/workers, trong đó cần tạo file: mp4_video_encoder.rb

class Mp4VideoEncoder
  include Sidekiq::Worker

  def perform(video_id)
    video = Video.find(video_id)
    path = video.video_file.path
    output = "/tmp/#{Time.now.getutc.to_f.to_s.delete('.')}.mp4"
    _command = `ffmpeg -i #{path} -f mp4 -vcodec h264 -acodec aac -strict -2 #{output}`
    if $?.to_i == 0
      video.mp4_file = File.open(output, 'r')
      video.save
      FileUtils.rm(output)
    else
      raise $?
    end
  end
end

Kịch bản rất đơn giản, ta lấy video từ db bằng id của nó, tạo ra đường dẫn đầu ra ngẫu nhiên và chạy lệnh shell để mã hoá sang dạng mp4. Nếu lệnh shell thành công, video được cập nhật với file mới và file video tạm tạo ra bởi ffmpeg sẽ bị xoá.

Hai file tiếp theo là gần giống nhau, nếu bạn muốn DRY hơn, bạn có thể viết module riêng. Dù sao thì 2 file tiếp theo sẽ là: ogv_video_encoder.rb

class OgvVideoEncoder
  include Sidekiq::Worker

  def perform(video_id)
    video = Video.find(video_id)
    path = video.video_file.path
    output = "/tmp/#{Time.now.getutc.to_f.to_s.delete('.')}.ogv"
    _command = `ffmpeg -i #{path} -codec:v libtheora -qscale:v 7 -codec:a libvorbis -qscale:a 7 #{output}`
    if $?.to_i == 0
      video.ogg_file = File.open(output, 'r')
      video.save
      FileUtils.rm(output)
    else
      raise $?
    end
  end
end

webm_video_encoder.rb

class WebmVideoEncoder
  include Sidekiq::Worker

  def perform(video_id)
    video = Video.find(video_id)
    path = video.video_file.path
    output = "/tmp/#{Time.now.getutc.to_f.to_s.delete('.')}.webm"
    _command = `ffmpeg -i #{path} -f webm -c:v libvpx -b:v 1M -c:a libvorbis #{output}`
    if $?.to_i == 0
      video.webm_file = File.open(output, 'r')
      video.save
      FileUtils.rm(output)
    else
      raise $?
    end
  end

end

File thứ 3 sẽ là một frame tại thời điểm 00:01:00 của video và lưu dưới dạng thu nhỏ (thumbnail). thumbnail_cutter.rb

class ThumbnailCutter
  include Sidekiq::Worker

  def perform(video_id)
    video = Video.find(video_id)
    output = "/tmp/#{Time.now.getutc.to_f.to_s.delete('.')}.png"
    _command = `ffmpeg -i #{video.video_file.path} -ss 00:00:01.000 -vframes 1 #{output}`
    if $?.to_i == 0
      video.thumbnail = File.open(output, 'r')
      video.save
      FileUtils.rm(output)
    else
      raise $?
    end
  end
end

Từ đây, bạn phải khởi động sidekiq cùng với rails server bằng cách vào consle và chạy lệnh `` $ bundle exec sidekiq

Khi muốn sử dụng ngay khi video được tải lên, cần chỉnh sửa Video model và thêm vào:
```ruby

after_create :run_encoders

private

def run_encoders
  ThumbnailCutter.perform_async(self.id)
  Mp4VideoEncoder.perform_async(self.id)
  OgvVideoEncoder.perform_async(self.id)
  WebmVideoEncoder.perform_async(self.id)
end

Thiếu một chút, video tải lên nhưng chưa được công bố, khi đó ta sẽ tạo trình làm việc để tìm kiếm những video chưa công bố và kiểm tra xem nó đã sẵn sàng để công bố hay chưa. Nếu có, sẽ công bố video và gửi thông báo. Tạo file app/workers/video_publisher.rb:


class VideoPublisher
  include Sidekiq::Worker
  include Sidetiq::Schedulable

  recurrence { minutely }

  def perform
    Video.where(published: false).each do |video|
      video.publish! if video.all_formats_encoded?
    end
  end
end

Bạn có thể thấy có thay đổi so với những xử lý trước. include Sidetiq::Schedulable - tạo lịch xử lý. reccurence { minutely } - xử lý trong từng phút.

Thông báo

Tạo 2 file trong thư mục app/assets/javascripts/: notifications.js.coffee.erb


$ ->
  if $("#user-notifications").length

    auth_key = $("#user-notifications").attr("data-pn-auth-key")
    notification_channel = $("#user-notifications").attr('data-pn-notification-channel')

    add_notifications = (msg) ->
          for notification in msg[0]
              switch notification.event
                  when 'published'
                      notification_html  = "<li><a href='/" + notification.scope + "/" + notification.id + "'>"
                      notification_html += "Your video " + notification.name + " has been published"
                      notification_html += "</a></li>"
                      $("#user-notifications .dropdown-menu").prepend notification_html

    window.pubnub = PUBNUB.init
          subscribe_key: "<%= Rails.application.secrets.pubnub_subscribe_key %>",
          publish_key: "<%= Rails.application.secrets.pubnub_publish_key %>",
          auth_key: auth_key

    window.pubnub.history
        channel: notification_channel,
        count: 10,
        reverse: false,
        callback: (msg) ->
            add_notifications(msg)

    window.pubnub.subscribe
        channel: notification_channel,
        callback: (msg) ->
            add_notifications(msg)

Đoạn script chạy sau khi document được load và có nút #user-notifications trong DOM. Nó khởi tạo pubnub client với user auth_key. Tải trước 10 thông báo từ người dùng, sau đó đăng ký kênh và chờ thông báo mới.

Và file thứ 2: videos.coffee


$ ->
  for video in $('.video-full, .video-thumb')
    window.pubnub.subscribe
      channel: 'video.' + $(video).attr('data-video-id')
      message: (msg, env, chan) ->
        id = chan.split('.')[1]
        switch msg.event
          when 'published'
            location.reload()
          when 'liked'
            for likes in $("[data-video-id=" + id + "]").find('.likes-count')
              console.log('add')
              $(likes).html(parseInt($(likes).html()) + 1)
          when 'disliked'
            for likes in $("[data-video-id=" + id + "]").find('.likes-count')
              console.log('remove')
              $(likes).html(parseInt($(likes).html()) - 1)

Đó là kịch bản đăng ký thông tin kênh về tất cả các video trên trang (có 2 loại phần tử video. Một là video đầy đủ, còn lại là video-thumb, cả 2 đều dùng data video id để xác định tên kênh đăng ký). Khi người dùng sử dụng video chưa được công bố, thì sẽ được đăng ký với kênh video không công bố, và khi khách hàng nhận được thông báo về video đang được xuất bản thì đơn giản chỉ là load lại trang. Hai sự kiện thích và không thích có thể xảy ra. Nó sẽ tìm kiếm video với id đã có và cập nhật số lượt thích.

Vậy là chúng ta đã tạo xong ứng dụng, bạn chạy rails serversidekiq server, sau đó truy cập localhost:3000. Bạn sẽ thấy giao diện chính thức của trang được hiển thị. Hãy chọn đăng ký và tạo mới tài khoản. Vậy là bạn có thể sử dụng ứng dụng được rồi.

Có thể thêm khá nhiều thay đổi trong ứng dụng trên, ví dụ thêm nhiều thông báo hơn, hay đánh dấu khi người dùng xem thông báo, tải video chất lượng hơn. Bạn có thể giới hạn lượt thích (giống như mỗi người dùng được thích 1 lần cho mỗi video).

Bạn có thể lấy mã nguồn đầy đủ của ứng dụng tại đây.

Nguồn: Building a Video Sharing App with Server Messaging in Ruby


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí