python-Django项目实战-播放与下载

歌曲播放与下载

从网站首页、歌曲排行榜和歌曲搜索得知,每个页面的歌曲信息都设置了歌曲播放的地址链接,只要在页面上单击歌曲名即可访问歌曲播放页。总的来说,歌曲播放页是音乐网站的核心页面,所有页面的歌曲信息都有播放链接,通过播放链接而已访问可以访问歌曲播放页。

定义路由

歌曲播放由项目应用play实现,在play的urls.py中分别定义路由play和download。

路由的定义过程如下:

python 复制代码
from django.urls import path
from .views import *
urlpatterns = [
    # 歌曲播放页
    path('<int:id>.html', playView, name='play'),
    # 歌曲下载
    path('download/<int:id>.html', downloadView, name='download')
]

视图定义

路由play和download设置路由变量id,该变量是模型Song的主键id,主要用于标记和区分当前播放的歌曲信息。路由play为用户提供在线试听、歌曲下载、歌曲点评链接和相关歌曲推荐,路由download用于实现歌曲下载功能。在play的view.py中分别定义视图函数playView和downloadView。

代码如下:

python 复制代码
from django.shortcuts import render
from django.http import StreamingHttpResponse
from index.models import *
​
def playView(request, id):
    # 热搜歌曲
    searchs = Dynamic.objects.select_related('song').order_by('-search').all()[:6]
    # 相关歌曲推荐
    type = Song.objects.values('type').get(id=id)['type']
    relevant = Dynamic.objects.select_related('song').filter(song__type=type).order_by('-plays').all()[:6]
    # 歌曲信息
    songs = Song.objects.get(id=int(id))
    # 播放列表
    play_list = request.session.get('play_list', [])
    exist = False
    if play_list:
        for i in play_list:
            if int(id) == i['id']:
                exist = True
    if exist == False:
        play_list.append({'id': int(id), 'singer': songs.singer, 'name': songs.name, 'time': songs.time})
    request.session['play_list'] = play_list
    # 歌词
    if songs.lyrics != '暂无歌词':
        lyrics = str(songs.lyrics.url)[1::]
        with open(lyrics, 'r', encoding='utf-8') as f:
            lyrics = f.read()
    # 添加播放次数
    # 功能扩展:可使用Session实现每天只添加一次播放次数
    p = Dynamic.objects.filter(song_id=int(id)).first()
    plays = p.plays + 1 if p else 1
    Dynamic.objects.update_or_create(song_id=id, defaults={'plays': plays})
    return render(request, 'play.html', locals())
​
​
def downloadView(request, id):
    # 添加下载次数
    p = Dynamic.objects.filter(song_id=int(id)).first()
    download = p.download + 1 if p else 1
    Dynamic.objects.update_or_create(song_id=id, defaults={'download': download})
    # 读取文件内容
    # 根据id查找歌曲信息
    songs = Song.objects.get(id=int(id))
    file = songs.file.url[1::]
    def file_iterator(file, chunk_size=512):
        with open(file, 'rb') as f:
            while True:
                c = f.read(chunk_size)
                if c:
                    yield c
                else:
                    break
    # 将文件内容写入StreamingHttpResponse对象
    # 并以字节流方式返回给用户,实现文件下载
    f = str(id) + '.m4a'
    response = StreamingHttpResponse(file_iterator(file))
    response['Content-Type'] = 'application/octet-stream'
    response['Content-Disposition'] = 'attachment; filename="%s"' %(f)
    return response

视图函数playView分别实现4次数据查询、播放列表的设置、歌词的读取和播放次数的累加,功能的实现过程说明如下:

(1)变量searchs实现歌曲搜索框下方的热搜歌曲,变量relevant实现相关歌曲推荐功能,将同一类型的歌曲展示在歌曲播放页的最下方。

(2)播放列表由会话Session存储当前用户的播放记录。

(3)变量songs获取当前歌曲的信息,如果当前歌曲存在歌词文件,就读取歌词文件的数据内容,并以变量lyrics表示。

(4)累加播放次数用于查询模型Dynamic是否存在歌曲动态信息,若存在,则将播放次数累加1,否则新增动态信息并将播放次数设为1,最后调用内置方法update_or_create实现动态信息的更新或新增操作。如果模型Dynamic不存在当前歌曲的动态信息,那么内置方法update_or_create将执行数据新增操作,否则执行数据更新操作。

视图函数downloadView实现歌曲文件的下载功能,歌曲每下载一次,就对歌曲的下载次数累加1,因此,视图函数downloadView实现两个功能;累计下载次数和文件下载,功能说明如下:

(1)累加下载次数与累加播放次数的功能相似,两者都是调用Django内置方法update_or_create实现动态信息的更新或新增操作,前者是操作模型字段download,后者是操作模型字段plays。

(2)文件下载使用StreamingHttpResponse实现,这是实现流式响应输出(流式响应输出是使用Python的迭代器将数据进行分段处理并传输)。

模板定义

核心就是音乐的播放部分

bash 复制代码
<audio id="myAudio" controls autoplay>
  <source src="{{ song.file.url }}" type="audio/mpeg">
</audio>

下载可以通过a标签

ini 复制代码
<a href="/user/test/xxxx.txt" download="文件名.txt">点击下载</a>
​
<button class="down">下载</button>

但是有个情况,比如txt、png、jpg等这些浏览器支持直接打开的文件是不会执行下载任务的,而是会直接打开文件,这个时候就需要给a标签添加一个属性"download"。

代码借鉴

xml 复制代码
{% extends "base.html"  %}
{% load static %}
​
{% block link  %}
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
<link rel="stylesheet" href="{% static "css/common.css" %}">
<link rel="stylesheet" href="{% static "css/play.css" %}">
{% endblock %}
​
{# ① #}
{% block body %}
<body>
    <div class="header">
        <a href="/" class="logo"><img src="{% static "image/logo.png" %}"></a>
        <div class="search-box">
            <!-- 歌曲搜索框 -->
            <form id="searchForm" action="{% url 'search' 1 %}" method="post">
            {% csrf_token %}
            <div class="search-keyword">
              <input id="kword" name="kword" type="text" class="keyword" maxlength="120">
            </div>
            <input id="subSerch" type="submit" class="search-button" value="搜 索" />
            </form>
            <div id="suggest" class="search-suggest"></div>
            <div class="search-hot-words">
                {% for s in searchs %}
                <a target="play" href="{% url 'play' s.song.id %}" >{{ s.song.name }}</a>
                {% endfor  %}
            </div>
        </div>
    </div><!--end header-->
    <div class="nav-box">
        <div class="nav-box-inner">
            <ul class="nav clearfix">
                <li><a href="{% url 'index' %}">首页</a></li>
                <li><a href="{% url 'ranking' %}" target="_blank">歌曲排行</a></li>
                <li><a href="{% url 'home' 1 %}" target="_blank">用户中心</a></li>
            </ul>
        </div>
    </div><!--end nav-box-->
    <div class="wrapper clearfix">
        <div class="content">
        
            {# ② #}
            <div class="product-detail-box clearfix">
                <div class="product-pics">
                    <div class="music_box">
                        <div id="jquery_jplayer_1" class="jp-jplayer" data-url={{ songs.file.url }}></div>
                        <div class="jp_img layz_load pic_po" title="点击播放"><img data-src={{ songs.img.url }}></div>
                        <div id="jp_container_1" class="jp-audio">
                            <div class="jp-gui jp-interface">
                                <div class="jp-time-holder clearfix">
                                    <div class="jp-progress">
                                        <div class="jp-seek-bar">
                                            <div class="jp-play-bar"></div>
                                        </div>
                                    </div>
                                    <div class="jp-time">
                                        <span class="jp-current-time"></span> /
                                        <span class="jp-duration"></span>
                                    </div>
                                </div>
                                <div class="song_error_corr" id="songCorr">
                                    <b class="err_btn">纠错</b>
                                    <ul>
                                        <li><span>歌词文本错误</span></li>
                                        <li><span>歌词时间错误</span></li>
                                        <li><span>歌曲错误</span></li>
                                    </ul>
                                </div>
                                <div class="jp-volume-bar">
                                    <div class="jp-volume-bar-value"></div>
                                </div>
                                <ul class="jp-controls clearfix">
                                    <li>
                                        <a class="jp-play" tabindex="1" title="play"></a>
                                        <a class="jp-pause" tabindex="1" title="pause"></a>
                                    </li>
                                    <li>
                                        <a class="jp-stop" tabindex="1" title="stop"></a>
                                    </li>
                                    <li>
                                        <a class="jp-repeat" tabindex="1" title="repeat"></a>
                                        <a class="jp-repeat-off" tabindex="1" title="repeat off"></a>
                                    </li>
                                    <li class="sound">
                                        <a class="jp-mute" tabindex="1" title="mute"></a>
                                        <a class="jp-unmute" tabindex="1" title="unmute"></a>
                                    </li>
                                </ul>
                            </div>
                        </div>
                        <div class="jplayer_content">
                            <ul id="lrc_list" class="lrc_list"></ul>
                        </div>
                    </div><!--end music_box-->
                    <textarea id="lrc_content" style="display: none;">
                      {{ lyrics }}
                    </textarea>
                </div><!--end product-pics-->
                <div class="product-detail-main">
                    <div class="product-price">
                        <h1 id="currentSong" >{{ songs.name }}</h1>
                        <div class="product-price-info">
                            <span>歌手:{{ songs.singer }}</span>
                        </div>
                        <div class="product-price-info">
                            <span>专辑:{{ songs.album }}</span>
                            <span>语种:{{ songs.languages }}</span>
                        </div>
                        <div class="product-price-info">
                            <span>流派:{{ songs.type }}</span>
                            <span>发行时间:{{ songs.release }}</span>
                        </div>
                    </div><!--end product-price-->
                    <div class="product-comment">
                        <div class="links clearfix">
                            <a class="minimum-link-A click_down" href="{% url 'download' songs.id %}">下载</a>
                            <a class="minimum-link-A" href="{% url 'comment' songs.id %}" >歌曲点评</a>
                        </div><!-- end links-->
                        {# ③ #}
                        <h3 class="list_title">当前播放列表</h3>
                        <ul class="playing-li" id="songlist">
                            <!--播放列表-->
                            {% for item in play_list %}
                            {%if item.id == songs.id %}
                            <li data-id="{{item.id}}" class="current">
                            {%else %}
                            <li data-id="{{item.id}}">
                            {%endif %}
                            <span class="num">{{forloop.counter}}</span>
                            <a class="name" href="{% url 'play' item.id %}" target="play" >{{item.name}}</a>
                            <a class="singer" href="javascript:;" target="_blank" >{{item.singer}}</a>
                            </li>
                            {% endfor %}
                        </ul>
                        <div class="nplayL-btns" id="playleixin">
                        <ul>
                            <li class="order current" data-run="order">
                                <a class="icon" href="javascript:void(0)" title="顺序播放"></a>
                            </li>
                            <li class="single" data-run="single">
                                <a class="icon" title="单曲循环" href="javascript:void(0)"></a>
                            </li>
                            <li class="random" data-run="random">
                                <a class="icon" title="随机播放" href="javascript:void(0)"></a>
                            </li>
                            <li class="next" data-run="next">
                                <a href="javascript:void(0)"><i></i>播放下一首</a>
                            </li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
​
            <div class="section">
                <div class="section-header">
                    <h3>相关歌曲</h3>
                </div>
                <div class="section-content">
                    <div class="parts-box">
                        <a href="javascript:;" target="_self" id="J_PartsPrev" class="prev-btn"><i></i></a>
                        <div class="parts-slider" id="J_PartsList">
                            <div class="parts-list-wrap f_w">
                                <ul id="" class="parts-list clearfix f_s">
                                    {% for item in relevant %}
                                    <li>
                                        {% if item.song.id != songs.id %}
                                        <a class="pic layz_load pic_po" href="{% url 'play' item.song.id %}" target="play" >
                                            <img data-src="{{ item.song.img.url }}">
                                        </a>
                                        <h4><a href="{% url 'play' item.song.id %}" target="play" >{{ item.song.name}}</a></h4>
                                        <a href="javascript:;" class="J_MoreParts accessories-more">{{ item.song.singer }}</a>
                                        {% endif %}
                                    </li>
                                    {% endfor %}
                                </ul>
                            </div>
                        </div>
                        <a href="javascript:;" target="_self" id="J_PartsNext" class="next-btn"><i></i></a>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script data-main="{% static "js/play.js" %}" src="{% static "js/require.js" %}"></script>
</body>
{% endblock  %}

我们将模板文件play.html实现的功能划分为3部分,在上述代码中已标注。

(1)标注①与之前的功能相同,不再重复讲解

(2)标注②实现了歌曲播放、歌曲信息、歌曲下载与歌曲点评。歌曲播放功能在class="jp-jplayer"的div标签里实现,标签属性data-url设置歌曲文件的路径地址,由JavaScript播放歌曲文件;歌曲信息在class="product-detail-main"的div标签里实现,而歌词动态效果在textarea文本框实现,歌曲的播放进度与歌词的滑动效果由JavaScript实现,歌曲下载与歌曲点评在class="links clearfix"的div标签中分别设置路由download和comment。

(3)标注③实现歌曲的播放列表和相关歌曲列表。歌曲的播放列表遍历模板上下文play_list生成数据列表,相关歌曲列表遍历模板上下文relevant生成相关歌曲列表。

歌曲点评

在歌曲播放页设置了歌曲点评的地址链接,单击"点评"按钮即可访问当前歌曲的点评页面。歌曲点评页实现两个功能:歌曲点评和歌曲点评信息列表,说明如下:

(1)歌曲点评是为用户提供歌曲点评功能,以表单的形式实现数据提交。

(2)歌曲点评信息列表是根据当前歌曲信息查找模型Comment的点评数据,并以数据列表的形式展示在网页。

定义路由

我们在项目应用comment中实现歌曲点评页,打开comment的urls.py定义歌曲点评的路由信息,代码如下:

javascript 复制代码
from django.urls import path
from .views import *
urlpatterns = [
    path('<int:id>.html', commentView, name='comment'),
]

定义视图

路由comment设置路由变量id,他代表模型Song的主键id,用于区分和识别当前歌曲的点评信息。路由的HTTP请求由视图函数commentView负责接收和处理,在comment的views.py中定义视图函数commentView,代码如下:

python 复制代码
from django.core.paginator import Paginator
from django.core.paginator import EmptyPage
from django.core.paginator import PageNotAnInteger
from django.shortcuts import render, redirect
from django.shortcuts import reverse
from django.http import Http404
from index.models import *
import time
​
​
def commentView(request, id):
    # 热搜歌曲
    searchs = Dynamic.objects.select_related('song').order_by('-search').all()[:6]
    # 点评内容的提交功能
    if request.method == 'POST':
        text = request.POST.get('comment', '')
        # 如果用户处于登录状态,则使用用户名,反之使用匿名用户
        if request.user.username:
            user = request.user.username
        else:
            user = '匿名用户'
        now = time.strftime('%Y-%m-%d', time.localtime(time.time()))
        if text:
            comment = Comment()
            comment.text = text
            comment.user = user
            comment.date = now
            comment.song_id = id
            comment.save()
        return redirect(reverse('comment', kwargs={'id': str(id)}))
    else:
        songs = Song.objects.filter(id=id).first()
        # 歌曲不存在抛出404异常
        if not songs:
            raise Http404('歌曲不存在')
        c = Comment.objects.filter(song_id=id).order_by('date')
        page = int(request.GET.get('page', 1))
        paginator = Paginator(c, 2)
        try:
            pages = paginator.page(page)
        except PageNotAnInteger:
            pages = paginator.page(1)
        except EmptyPage:
            pages = paginator.page(paginator.num_pages)
        return render(request, 'comment.html', locals())

视图函数commentView分别对GET请求和POST请求执行不同的处理,并且将路由变量id作为函数参数id。当我们从歌曲播放页进入歌曲点评页,浏览器访问歌曲点评页,相当于向网站发送GET请求,视图函数commentView执行以下处理:

(1)视图函数commentView将函数参数id作为模型Song的查询条件,如果模型Song没有记录当前歌曲,就抛出404异常并提示歌曲不存在。

(2)如果模型Song存在当前歌曲,就将函数参数id作为模型Comment的查询条件,查询当前歌曲的点评信息,查询结果命名为c。然后从GET请求中获取请求参数page,并且生成变量page,如果请求参数page不存在,变量page的值就设为1。

(3)最后将变量page和查询结果c进行分页处理,生成分页对象pages,并调用模板文件comment.html生成歌曲点评页。

如果我们在歌曲点评页填写点评内容并单击"发布"按钮,浏览器就向网站发送POST请求,视图函数commentView将会接收一个POST请求,并执行以下处理:

(1)从网页表单中获取点评内容,并将点评内容命名为变量text。然后获取当前用户名,如果当前用户处于未登录状态,那么用户名设为匿名用户,并且以变量user表示。

(2)如果变量text不为空,就在模型Comment中新增一条点评信息,分别记录点评内容、用户名和点评日期,模型Comment的外键字段song设为函数参数id,这是为当前歌曲新增一条点评信息。

(3)最后重定向歌曲点评页,以GET请求方式访问歌曲点评页,将新增的点评信息显示在歌曲点评页,网站的重定向可以防止表单多次提交,解决同一条点评信息重复创建的问题。

定义模型

下一步在模板文件comment.html中编写相应的网页内容。实现5个功能:歌曲搜索框、网站导航栏功能、歌曲点评框、歌曲点评信息列表和分页导航功能。

xml 复制代码
{% extends "base.html"  %}
{% load static %}
​
{% block link %}
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
<link rel="stylesheet" href="{% static "css/common.css" %}">
<link rel="stylesheet" href="{% static "css/comment.css" %}">
{% endblock %}
​
{% block body  %}
<body class="review">
<div class="header">
<a href="/" class="logo"><img src="{% static "image/logo.png" %}"></a>
<div class="search-box">
    <form id="searchForm" action="{% url 'search' 1 %}" method="post">
        {% csrf_token %}
        <div class="search-keyword">
            <input id="kword" name="kword" type="text" class="keyword" maxlength="120">
        </div>
        <input id="subSerch" type="submit" class="search-button" value="搜 索"/>
    </form>
    <div id="suggest" class="search-suggest"></div>
    <div class="search-hot-words">
        {% for s in searchs %}
            <a target="play" href="{% url 'play' s.song.id %}" >{{ s.song.name }}</a>
        {% endfor  %}
    </div>
</div>
</div><!--end header-->
<div class="nav-box">
<div class="nav-box-inner">
    <ul class="nav clearfix">
        <li><a href="{% url 'index' %}">首页</a></li>
        <li><a href="{% url 'ranking' %}" target="_blank">歌曲排行</a></li>
        <li><a href="{% url 'home' 1 %}" target="_blank">用户中心</a></li>
    </ul>
</div>
</div><!--end nav-box-->
<div class="wrapper">
<div class="breadcrumb">
    <a href="/">首页</a> &gt;
            <a href="{% url 'play' id %}" target="_self">{{songs.name}}</a> &gt;
    <span>点评</span>
</div>
<div class="page-title" id="currentSong"></div>
</div>
<div class="wrapper">
<div class="section">
    <div class="section-header"><h3 class="section-title">网友点评</h3></div>
    <div class="section-content comments-score-new review-comments-score clearfix">
        <div class="clearfix">
​
        <!--点评框-->
            <div class="comments-box">
            <div class="comments-box-title">我要点评<<{{ songs.name }}>></div>
            <div class="comments-default-score clearfix"></div>
            <form action="" method="post" id="usrform">
                {% csrf_token %}
                <div class="writebox">
                    <textarea name="comment" form="usrform"></textarea>
                </div>
                <div class="comments-box-button clearfix">
                <input type="submit" value="发布" class="_j_cc_post_entry cc-post-entry" id="scoreBtn">
                <div data-role="user-login" class="_j_cc_post_login"></div>
                </div>
                <div id="scoreTips2" style="padding-top:10px;"></div>
            </form>
            </div>
        </div>
    </div>
</div>
</div>
​
​
<div class="wrapper clearfix">
<div class="content">
<div id="J_CommentList">
<ul class="comment-list">
    {% for item in pages.object_list  %}
    <li class="comment-item ">
        <div class="comments-user">
        <span class="face">
        <img src="{% static "image/user.jpg" %}" width="60" height="60">
        </span>
        </div>
        <div class="comments-list-content">
        <div class="single-score clearfix">
            <span class="date">{{ item.date }}</span>
            <div><span class="score">{{ item.user }}</span></div>
        </div>
        <!--comments-content-->
        <div class="comments-content">
            <div class="J_CommentContent comment-height-limit">
                <div class="content-inner">
                    <div class="comments-words">
                        <p>{{ item.text }}</p>
                    </div>
                </div>
            </div>
        </div>
        </div>
    </li>
    {% endfor  %}
</ul>
<div class="page-box">
    <div class="pagebar" id="pageBar">
        {% if pages.has_previous %}
            <a href="{% url 'comment' id %}?page={{ pages.previous_page_number }}" class="prev" target="_self"><i></i>上一页</a>
        {% endif %}
        {% for page in pages.paginator.page_range %}
            {% if pages.number == page %}
                <span class="sel">{{ page }}</span>
            {% else %}
                <a href="{% url 'comment' id %}?page={{ page }}" target="_self">{{ page }}</a>
            {% endif %}
        {% endfor %}
        {% if pages.has_next %}
            <a href="{% url 'comment' id %}?page={{ pages.next_page_number }}" class="next" target="_self"><i></i>下一页</a>
        {% endif %}
    </div>
</div>
</div>
</div>
</div>
</body>
{% endblock  %}
相关推荐
苏三的开发日记6 分钟前
linux端进行kafka集群服务的搭建
后端
test管家20 分钟前
PyTorch动态图编程与自定义网络层实战教程
python
苏三的开发日记25 分钟前
windows系统搭建kafka环境
后端
laocooon52385788632 分钟前
python 收发信的功能。
开发语言·python
爬山算法35 分钟前
Netty(19)Netty的性能优化手段有哪些?
java·后端
Tony Bai36 分钟前
Cloudflare 2025 年度报告发布——Go 语言再次“屠榜”API 领域,AI 流量激增!
开发语言·人工智能·后端·golang
清水白石00839 分钟前
《Python 责任链模式实战指南:从设计思想到工程落地》
开发语言·python·责任链模式
沛沛老爹1 小时前
Web开发者快速上手AI Agent:基于LangChain的提示词应用优化实战
人工智能·python·langchain·提示词·rag·web转型
宁大小白1 小时前
pythonstudy Day39
python·机器学习
想用offer打牌1 小时前
虚拟内存与寻址方式解析(面试版)
java·后端·面试·系统架构